SQL注入的实质就是通过SQL拼接字符串追加命令,导致SQL的语义发生了变化。为什么发生了改变呢? 因为没有重用以前的执行计划,而是对注入后的SQL语句重新编译,然后重新执行了语法解析。 所以要保证SQL语义不变,(即想要表达SQL本身的语义,并不是注入后的语义)就必须保证执行计划确定不被改变。
SQL注入的表现
示例
1.我们先熟悉一下表里的东西
2.要查询的原SQL语句。
3.注入的SQL语句
分析示例:
原SQL语句
select COUNT(*) from T_Users where UserName = ‘a‘ and Password =‘b‘1
语义:要查找(用户名为‘a’,密码为‘b’) 的用户总共有几个. 查询结果为1
注入后的SQL语句
select COUNT(*) from T_Users where UserName = ‘a‘ and Password = ‘b‘ or 1=1 --‘1
注入后的语义:查找 (密码是a的,并且用户名是b的,) 或者1=1 的所有用户的数量。 这时候语义发生了改变,查询结果为4
分析:如果用户在输入密码时, 输入的不是b 而是b’ or 1=1- - ,语义将会发生改变,(- -在数据库中是注释的标记)最后查询的结果当然也就不相同了。原语句的查询结果为1,注入后的查询结果为4。
SQL注入的危害
我们刚刚示范了SQL注入带来的查询结果的改变,这个可能会让别有用心的用户熟知我们数据库中的记录条数。或者如果输入的是b’ delect T_Users - - 这时候我们整张表都被删除了,这个带来的后果可想而知。
防止SQL注入–参数化查询
我们的机房重构中D层一直在用参数化查询,但是我当时没有完全理解用参数化查询的用意。直到现在才理解了为什么要用参数化查询和参数化查询如何防止SQL注入的。
public int checkUser(Users user)
{
string sql = "select COUNT(*) from T_Users where UserName = @UserName and Password = @Password";
SqlParameter[] paras ={new SqlParameter ("@UserName",User.username) ,
new SqlParameter ("@Password",User.password)};
int num = sqlhelper.ExecuteNonQuery(sql, paras);
return num ;
}12345678
这是我们用了参数化查询后的效果,我们来分析一下参数化查询是如何防止sql注入的。
select count(*) from T_Users where UserName = ‘a’ and Password = ‘b’ or 1=1- -‘,
是等到 UserName 参数传过来,才会去编译这条 sql语句(sql语句是编译后执行的),而这个时候, 这个拼接的sql 多了 or 选项,这改变了sql语句的本意。
是等到 UserName 参数传过来,才会去编译这条 sql语句(sql语句是编译后执行的),而这个时候, 这个拼接的sql 多了 or 选项,这改变了sql语句的本意。
参数化查询:是先编译 这样一条sql, select count (*) from T_Users where = UserName = @UserName and Password = @Password ,和函数的编译一样, 这样的话,以后’b’ or 1=1- - ’ 只能作为 Password 的参数了,,,相当于你去查 ” ‘b’ or 1=1—’ ” 这个密码,而不是 查 “b” 这个密码 或者 ” 1=1 ” 了。 相当于把 or 给屏蔽掉了。or 原来是sql的一项(普通的拼接),现在就是个普通字符串参数(参数化查询)
,不能改变sql语义,也不能改变编译执行计划,所以防止了SQL注入。
,不能改变sql语义,也不能改变编译执行计划,所以防止了SQL注入。
示例小结:
正常的命令是程序中的“常量”,能动手脚的只能是可输入的用户名、密码这些可输入的“变量”。 拼接方式是将常量、变量拼在一起编译,才可以在正常的命令后追加命令。参数化的方式仅常量参与编译,就无法追加命令,防止了sql注入。
总结:
参数化查询的方式简单来说就是指用参数参与编译,这样就全是常量了,编译执行计划时没有用到参数,所以命令是确定的。编译后计划便确定了,编译后传参数知识为了运行执行计划,执行计划不变,就不会多出命令,SQL语义就无法改变,防止了sql注入。