类型:网络安全大小:6.2M语言:多语言【中文】评分:2.5标签:立即下载很多人都知道SQL注入,也知道SQL参数化查询可以防SQL注入,但是为什么能防SQL注入很多人不知道。这篇文章主要讲的就是这个问题,也许你在一些文章里看到过这个内容,但是看一下也没有坏处。
首先,我们需要知道SQL在收到指令后会做什么:
详见文章:sql server编译、重编译及执行计划重用原理。这里我简单表述为:接收指令-编译SQL生成执行计划-选择执行计划-执行执行计划。
细节可能有点不同,但大致步骤如上所示。然后我们来分析为什么拼接SQL字符串会导致SQL注入风险。
首先,创建一个用户表:
创建表格[dbo]。[用户]([标识][唯一标识符]不为空、[用户标识] [int]不为空、[用户名] [varchar](50)为空、[密码] [varchar](50)不为空、约束[PK_Users]主键聚集([标识] ASC)WITH (PAD_INDEX=OFF、STATISTICS _ NORECOMPUTE=OFF、IGNORE_DUP_KEY=OFF、ALLOW _ ROW _ LOCKS=ON、ALLOW _ PAGE _ lock=ON)ON[PRIMARY])ON[PRIMARY]
插入一些数据:
插入[测试]。[dbo]。[用户]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),1,' name1 ',' pwd 1 ');插入[测试]。[dbo]。[用户]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),2,' name2 ',' pwd 2 ');插入[测试]。[dbo]。[用户]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),3,' name3 ',' pwd 3 ');插入[测试]。[dbo]。[用户]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),4,' name4 ',' pwd 4 ');插入[测试]。[dbo]。[用户]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),5,' name5 ',' pwd 5 ');假设我们有一个用户登录页面,代码如下:
用于验证用户登录的Sql如下:
从密码=' a '和用户名=' b '的用户中选择count (*)。此代码返回密码和用户名匹配的用户数。如果大于1,则表示用户存在。
本文不讨论SQL中的密码策略,也不讨论代码规范,而是主要讨论为什么可以防止SQL注入。请部分同学不要纠结一些与SQL注入无关的代码或话题。
您可以看到执行结果:
这是由SQL概要文件跟踪的SQL语句。
注入的代码如下:
从密码=' a '和用户名=' b '或1=1-这里有人将用户名设置为" b '或1=1"的用户中选择count (*)。
实际执行的SQL如下所示:
很明显,SQL注入是成功的。
很多人都知道参数化查询可以避免上面的注入问题,比如下面的代码:
类程序{私有静态字符串connectionString='数据源='。初始目录=测试;集成安全性=真';静态void Main(字符串[] args) { Login('b ',' a ');登录(' b '或1=1 -',' a ');}私有静态void Login(字符串userName,字符串password) {使用(SqlConnection conn=new SqlConnection(connectionString)){ conn . Open();SqlCommand comm=new SqlCommand();通信连接=连接;//添加一个参数comm.commandtext='从用户中选择count (*),其中password=@ password,username=@ username '用于每条数据;comm.Parameters.AddRange(新的SqlParameter[]{新的SqlParameter('@Password ',SqlDbType。VarChar) { Value=password},新的SqlParameter('@UserName ',SqlDbType。VarChar) { Value=userName},});comm . executionquery();{ } }实际执行的SQL如下:
exec sp _ executesql ' N从密码=@密码和用户名=@用户名的用户中选择COUNT(*),N'@Password varchar(1),@UserName varchar(1),@Password='a ',@ UserName=' b ' exec sp _ executesql N '从密码=@密码和用户名=@用户名的用户中选择COUNT(*),N'@Password varchar(1),@UserName varchar(11),@Password='a ',@ UserName=' b '或1=1—'可以看到参数化查询主要做了这些事情:
1:参数过滤,可以看到@UserName='b '或1=1—'2:执行计划重用因为执行计划被重用,所以可以防止结构化查询语言注入。
首先分析结构化查询语言注入的本质,用户写了一段结构化查询语言用来表示查找密码是a的,用户名是b的所有用户的数量。通过注入SQL,这段结构化查询语言现在表示的含义是查找(密码是a的,并且用户名是b的,) 或者1=1 的所有用户的数量。
可以看到结构化查询语言的语意发生了改变,为什么发生了改变呢?因为没有重用以前的执行计划,因为对注入后的结构化查询语言语句重新进行了编译,因为重新执行了语法解析。所以要保证结构化查询语言语义不变,即我想要表达结构化查询语言就是我想表达的意思,不是别的注入后的意思,就应该重用执行计划。
如果不能够重用执行计划,那么就有结构化查询语言注入的风险,因为结构化查询语言的语意有可能会变化,所表达的查询就可能变化。
在数据库中查询执行计划可以使用下面的脚本:
数据库一致性检查程序自由进程缓存选择总运行时间/执行次数平均时间,total _ logic _ reads/execution _ count逻辑读,usecounts重用次数,SUBSTRING(d.text,(语句_start_offset/2) 1,((案例语句_ END _ offset WHEN-1然后数据长度(文本)ELSE语句_end_offset END -语句_start_offset)/2) 1)语句执行从sys.dm_exec_cached_plans跨应用系统。DM _ exec _ query _ plan。DM _ exec _ query _ stats bcross apply sys。DM _ exec _ SQL _ text(b . SQL _ handle)d-其中a.plan_handle=b.plan_handle和total _ logic _ reads/execution _ count 4000 order BY total _ used _ time/execution _ count desc;
在这篇文章中有这么一段:
这里作者有一句话:"不过这种写法和直接拼结构化查询语言执行没啥实质性的区别",任何拼接结构化查询语言的方式都有结构化查询语言注入的风险,所以如果没有实质性的区别的话,那么使用高级管理人员动态执行结构化查询语言是不能防止结构化查询语言注入的。
比如下面的代码:
私有静态void TestMethod(){ 0使用(SqlConnection conn=new SqlConnection(connectionString)){ conn . Open();SqlCommand comm=new SqlCommand();通信连接=连接;//使用高级管理人员动态执行SQL //实际执行的查询计划为(@UserID varchar(最大值))从用户(nolock)中选择*其中使用者辩证码在(1,2,3,4) //不是预期的(@ UserID varchar(max))exec('从用户中选择*(无锁定),其中使用者辩证码在(' @ UserID ')')comm . CommandText=' exec '(从用户中选择*(无锁定),其中使用者辩证码在(“@ UserID”)”)“)”;通信参数.添加(新的SqlParameter('@UserID ',SqlDbType .VarChar,-1) { Value='1,2,3,4 ' });//comm.Parameters.Add(新的SqlParameter('@UserID ',SqlDbType .VarChar,-1) { Value='1,2,3,4 ';从用户中删除;- ' });comm . execution query();}}执行的结构化查询语言如下:
exec sp _ executesql N ' exec('从用户中选择*)(无锁定),其中UserID in(' @ UserID ' ')' ')',N'@UserID varchar(max)',@UserID='1,2,3,4 '
可以看到结构化查询语言语句并没有参数化查询。如果你将使用者辩证码设置为"1,2,3,4);从用户中删除;—-
",那么执行的结构化查询语言就是下面这样:exec sp _ executesql N ' exec('从用户中选择*)(无锁定),其中UserID in(' @ UserID ' ')')'、N'@UserID varchar(max)'、@UserID='1,2,3,4 ');从用户中删除;- '不要以为加了个@UserID就代表能够防止结构化查询语言注入,实际执行的结构化查询语言如下:
任何动态的SQL执行都有注入的风险,因为动态意味着不重用执行计划,如果不重用执行计划,基本上不能保证你写的SQL所表达的意思就是你想表达的意思。就像小时候填空一样,找密码为(_ _ _ _)用户名为(_ _ _ _)的用户。不管你填什么值,我就是这个意思。最后总结一句话:因为参数化查询可以重用执行计划,如果重用执行计划,那么SQL表达的语义就不会改变,所以可以防止SQL注入。如果执行计划不能重复使用,可能会出现SQL注入。存储过程也是如此,因为执行计划可以重用。