3.0如何防止SQL注入
3.1理论(注入防范)
SQL注入攻击的危害很大,而且防火墙很难对攻击行为进行拦截,主要的SQL注入攻击防范方法,具体有以下几个方面。
1、分级管理
对用户进行分级管理,严格控制用户的权限,对于普通用户,禁止给予数据库建立、删除、修改等相关权限,只有系统管理员才具有增、删、改、查的权限。例如上述实例中用户在查询语句中加入了drop table。肯定是不能让其执行的,否则系统的数据库安全性就无法保障。故而通过权限的设计限制。使得即使恶意攻击者在数据提交时嵌入了相关攻击代码。但因为设置了权限,从而使得代码不能执行。从而减少SQL注入对数据库的安全威胁。
2、参数传值
程序员在书写SQL语言时,禁止将变量直接写入到SQL语句,必须通过设置相应的参数来传递相关的变量。从而抑制SQL注入。数据输入不能直接嵌入到查询语句中。同时要过滤输入的内容,过滤掉不安全的输入数据。或者采用参数传值的方式传递输入变量。这样可以最大程度防范SQL注入攻击。
3、基础过滤与二次过滤
SQL注入攻击前,入侵者通过修改参数提交“and”等特殊字符,判断是否存在漏洞,然后通过select、update等各种字符编写SQL注入语句。因此防范SQL注入要对用户输入进行检查,确保数据输入的安全性,在具体检查输入或提交的变量时,对于单引号、双引号、冒号等字符进行转换或者过滤,从而有效防止SQL注入。当然危险字符有很多,在获取用户输入提交的参数时,首先要进行基础过滤,然后根据程序的功能及用户输入的可能性进行二次过滤,以确保系统的安全性。
4、使用安全参数
SQL数据库为了有效抑制SQL注入攻击的影响。在进行SQLServer数据库设计时设置了专门的SQL安全参数。在程序编写时应尽量使用安全参数来杜绝注入式攻击。从而确保系统的安全性。
SQLServer数据库提供了Parameters集合,它在数据库中的功能是对数据进行类型检查和长度验证,当程序员在程序设计时加入了Parameters集合,系统会自动过滤掉用户输入中的执行代码,识别其为字符值。如果用户输入中含有恶意的代码,数据库在进行检查时也能够将其过滤掉。同时Parameters集合还能进行强制执行检查。一旦检查值超出范围。系统就会出现异常报错,同时将信息发送系统管理员,方便管理员做出相应的防范措施。
5、漏洞扫描
为了更有效地防范SQL注入攻击,作为系统管理除了设置有效的防范措施,更应该及时发现系统存在SQL攻击安全漏洞。系统管理员可以通过采购一些专门系统的SQL漏洞扫描工具,通过专业的扫描工具,可以及时的扫描到系统存在的相应漏洞。虽然漏洞扫描工具只能扫描到SQL注入漏洞,不能防范SQL注入攻击。但系统管理员可以通过扫描到的安全漏洞,根据不同的情况采取相应的防范措施封堵相应的漏洞,从而把SQL注入攻击的门给关上,从而确保系统的安全。
6、多层验证
现在的网站系统功能越来越庞大复杂。为确保系统的安全,访问者的数据输入必须经过严格的验证才能进入系统,验证没通过的输入直接被拒绝访问数据库,并且向上层系统发出错误提示信息。同时在客户端访问程序中验证访问者的相关输入信息,从而更有效的防止简单的SQL注入。但是如果多层验证中的下层如果验证数据通过,那么绕过客户端的攻击者就能够随意访问系统。因此在进行多层验证时,要每个层次相互配合,只有在客户端和系统端都进行有效的验证防护,才能更好地防范SQL注入攻击。
7、数据库信息加密
传统的加解密的方法大致可以分为三种:
(1)对称加密:即加密方和解密方都使用相同的加密算法和密钥,这种方案的密钥的保存非常关键,因为算法是公开的,而密钥是保密的,一旦密匙泄露,黑客仍然可以轻易解密。常见的对称加密算法有:AES、DES等。
(2)非对称加密:即使用不同的密钥来进行加解密,密钥被分为公钥和私钥,用私钥加密的数据必须使用公钥来解密,同样用公钥加密的数据必须用对应的私钥来解密,常见的非对称加密算法有:RSA等。
(3)不可逆加密:利用哈希算法使数据加密之后无法解密回原数据,这样的哈希算法常用的有:md5、SHA-1等。
3.2代码示例
3.2.1 PreparedStatement对象
PreparedStatement可以防止SQL注入,效率更好
PreparedStatement使用?占位符代替参数进行预编译不执行也防止sql注入;
3.2.2 新增
正常版本
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * @ClassName: TestInsert * @Description: TODO 类描述 */ public class TestInsert { public static void main(String[] args) { Connection con = null; Statement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); st = con.createStatement(); String sql = "INSERT INTO users(`id`,`name`,`password`,`email`,`birthday`)\n" + "VALUES (5,'钱七','123456','qianqi@sina.com','1988-12-04')"; int num = st.executeUpdate(sql); if (num > 0) { System.out.println("插入成功!"); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
预编译版本
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @ClassName: TestInsert * @Description: TODO 类描述 */ public class TestInsert { public static void main(String[] args) { Connection con = null; PreparedStatement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); //使用?占位符代替参数 String sql = "INSERT INTO users(`id`,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)"; //预编译SQL,先写SQL,然后不执行 st = con.prepareStatement(sql); //手动给参数赋值 st.setInt(1, 5); st.setString(2, "钱七"); st.setString(3, "123456"); st.setString(4, "qianqi@sina.com"); st.setDate(5, new java.sql.Date(new java.util.Date().getTime())); int num = st.executeUpdate(); if (num > 0) { System.out.println("插入成功!"); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
3.2.3 删除
同理也是使用预编译占位符 ?代替参数
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @ClassName: TestDelete * @Description: TODO 类描述 */ public class TestDelete { public static void main(String[] args) { Connection con = null; PreparedStatement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); //使用?占位符代替参数 String sql = "DELETE FROM users WHERE `id`=?"; //预编译SQL,先写SQL,然后不执行 st = con.prepareStatement(sql); //手动给参数赋值 st.setInt(1, 5); int num = st.executeUpdate(); if (num > 0) { System.out.println("删除成功!"); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
3.2.3 修改
同理
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @ClassName: TestUpdate * @Description: TODO 类描述 */ public class TestUpdate { public static void main(String[] args) { Connection con = null; PreparedStatement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); //使用?占位符代替参数 String sql = "UPDATE users SET birthday=? WHERE id=?"; //预编译SQL,先写SQL,然后不执行 st = con.prepareStatement(sql); //手动给参数赋值 st.setDate(1, new java.sql.Date(new java.util.Date().getTime())); st.setInt(2, 1); int num = st.executeUpdate(); if (num > 0) { System.out.println("修改成功!"); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
3.2.3 查询
同理
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @ClassName: TestSelect * @Description: TODO 类描述 */ public class TestSelect { public static void main(String[] args) { Connection con = null; PreparedStatement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); //使用?占位符代替参数 String sql = "SELECT * FROM users WHERE id=?"; //预编译SQL,先写SQL,然后不执行 st = con.prepareStatement(sql); //手动给参数赋值 st.setInt(1, 1); rs = st.executeQuery(); while (rs.next()) { System.out.println("id="+rs.getInt("id")); System.out.println("name="+rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
3.2.3 解决2.0SQL注入demo
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * @ClassName: SQLQuestion * @Description: TODO 类描述 */ public class SQLQuestion { public static void main(String[] args) { //正常登录 // login("张三","1234567"); //sql注入 login("' or '1=1","123456"); } /** * 登录业务 */ public static void login(String userName, String password) { Connection con = null; Statement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); st = con.createStatement(); String sql = "SELECT * FROM users WHERE `name`='"+userName+"' AND `password`='"+password+"'"; // SELECT * FROM users WHERE `name`='' or '1=1' AND `password`='123456' System.out.println(sql); rs = st.executeQuery(sql); while (rs.next()) { System.out.println("id="+rs.getInt("id")); System.out.println("name="+rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
修改后
PreparedStatement 防止SQL注入的本质,把传递进来的参数当做字符
假设其中存在转义字符, 就忽略 比如说’会被直接转义
import com.zyy.lesson02.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @ClassName: SQLQuestion * @Description: TODO 类描述 */ public class SQLQuestion { public static void main(String[] args) { //正常登录 // login("张三","123456"); //sql注入 login("' or '1=1", "123456"); } /** * 登录业务 */ public static void login(String userName, String password) { Connection con = null; PreparedStatement st = null; ResultSet rs = null; try { con = JDBCUtils.getConnection(); // PreparedStatement 防止SQL注入的本质,把传递进来的参数当做字符 // 假设其中存在转义字符, 就忽略 比如说'会被直接转义 String sql = "SELECT * FROM users WHERE `name`=? AND `password`=?"; st = con.prepareStatement(sql); st.setString(1, userName); st.setString(2, password); rs = st.executeQuery(); while (rs.next()) { System.out.println("id=" + rs.getInt("id")); System.out.println("name=" + rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(con, st, rs); } } }
预编译之后就无法进行sql注入的查询;