参数化查询为什么能够防止SQL注入 (转)
很多人都知道SQL注入,也知道SQL参数化查询可以防止SQL注入,可为什么能防止注入却并不是很多人都知道的。
本文主要讲述的是这个问题,也许你在部分文章中看到过这块内容,当然了看看也无妨。
首先:我们要了解SQL收到一个指令后所做的事情:
具体细节可以查看文章:Sql Server 编译、重编译与执行计划重用原理
在这里,我简单的表示为: 收到指令 -> 编译SQL生成执行计划 ->选择执行计划 ->执行执行计划。
具体可能有点不一样,但大致的步骤如上所示。
接着我们来分析为什么拼接SQL 字符串会导致SQL注入的风险呢?
首先创建一张表Users:
CREATE TABLE [dbo].[Users]( [Id] [uniqueidentifier] NOT NULL, [UserId] [int] NOT NULL, [UserName] [varchar](50) NULL, [Password] [varchar](50) NOT NULL, CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
插入一些数据:
INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),1,'name1','pwd1');
INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),2,'name2','pwd2');
INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),3,'name3','pwd3');
INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),4,'name4','pwd4');
INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),5,'name5','pwd5');
假设我们有个用户登录的页面,代码如下:
验证用户登录的sql 如下:
select COUNT(*) from Users where Password = 'a' and UserName = 'b'
这段代码返回Password 和UserName都匹配的用户数量,如果大于1的话,那么就代表用户存在。
本文不讨论SQL 中的密码策略,也不讨论代码规范,主要是讲为什么能够防止SQL注入,请一些同学不要纠结与某些代码,或者和SQL注入无关的主题。
可以看到执行结果:
这个是SQL profile 跟踪的SQL 语句。
注入的代码如下:
select COUNT(*) from Users where Password = 'a' and UserName = 'b' or 1=1—'
这里有人将UserName设置为了 “b' or 1=1 –”.
实际执行的SQL就变成了如下:
可以很明显的看到SQL注入成功了。
很多人都知道参数化查询可以避免上面出现的注入问题,比如下面的代码:
class Program
{
private static string connectionString = "Data Source=.;Initial Catalog=Test;Integrated Security=True"; static void Main(string[] args)
{
Login("b", "a");
Login("b' or 1=1--", "a");
} private static void Login(string userName, string password)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//为每一条数据添加一个参数
comm.CommandText = "select COUNT(*) from Users where Password = @Password and UserName = @UserName";
comm.Parameters.AddRange(
new SqlParameter[]{
new SqlParameter("@Password", SqlDbType.VarChar) { Value = password},
new SqlParameter("@UserName", SqlDbType.VarChar) { Value = userName},
}); comm.ExecuteNonQuery();
}
}
}
实际执行的SQL 如下所示:
exec sp_executesql N'select COUNT(*) from Users where Password = @Password and UserName = @UserName',N'@Password varchar(1),@UserName varchar(1)',@Password='a',@UserName='b'
exec sp_executesql N'select COUNT(*) from Users where Password = @Password and UserName = @UserName',N'@Password varchar(1),@UserName varchar(11)',@Password='a',@UserName='b'' or 1=1—'
可以看到参数化查询主要做了这些事情:
1:参数过滤,可以看到 @UserName='b'' or 1=1—'
2:执行计划重用
因为执行计划被重用,所以可以防止SQL注入。
首先分析SQL注入的本质,
用户写了一段SQL 用来表示查找密码是a的,用户名是b的所有用户的数量。
通过注入SQL,这段SQL现在表示的含义是查找(密码是a的,并且用户名是b的,) 或者1=1 的所有用户的数量。
可以看到SQL的语意发生了改变,为什么发生了改变呢?,因为没有重用以前的执行计划,因为对注入后的SQL语句重新进行了编译,因为重新执行了语法解析。所以要保证SQL语义不变,即我想要表达SQL就是我想表达的意思,不是别的注入后的意思,就应该重用执行计划。
如果不能够重用执行计划,那么就有SQL注入的风险,因为SQL的语意有可能会变化,所表达的查询就可能变化。
在SQL Server 中查询执行计划可以使用下面的脚本:
DBCC FreeProccache select total_elapsed_time / execution_count 平均时间,total_logical_reads/execution_count 逻辑读,
usecounts 重用次数,SUBSTRING(d.text, (statement_start_offset/2) + 1,
((CASE statement_end_offset
WHEN -1 THEN DATALENGTH(text)
ELSE statement_end_offset END
- statement_start_offset)/2) + 1) 语句执行 from sys.dm_exec_cached_plans a
cross apply sys.dm_exec_query_plan(a.plan_handle) c
,sys.dm_exec_query_stats b
cross apply sys.dm_exec_sql_text(b.sql_handle) d
--where a.plan_handle=b.plan_handle and total_logical_reads/execution_count>4000
ORDER BY total_elapsed_time / execution_count DESC;
博客园有篇文章: Sql Server参数化查询之where in和like实现详解
在这篇文章中有这么一段:
这里作者有一句话:”不过这种写法和直接拼SQL执行没啥实质性的区别”
任何拼接SQL的方式都有SQL注入的风险,所以如果没有实质性的区别的话,那么使用exec 动态执行SQL是不能防止SQL注入的。
比如下面的代码:
private static void TestMethod()
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//使用exec动态执行SQL
//实际执行的查询计划为(@UserID varchar(max))select * from Users(nolock) where UserID in (1,2,3,4)
//不是预期的(@UserID varchar(max))exec('select * from Users(nolock) where UserID in ('+@UserID+')')
comm.CommandText = "exec('select * from Users(nolock) where UserID in ('+@UserID+')')";
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
//comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4); delete from Users;--" });
comm.ExecuteNonQuery();
}
}
执行的SQL 如下:
exec sp_executesql N'exec(''select * from Users(nolock) where UserID in (''+@UserID+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4'
可以看到SQL语句并没有参数化查询。
如果你将UserID设置为”
1,2,3,4); delete from Users;—-
”,那么执行的SQL就是下面这样:
exec sp_executesql N'exec(''select * from Users(nolock) where UserID in (''+@UserID+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4); delete from Users;--'
不要以为加了个@UserID 就代表能够防止SQL注入,实际执行的SQL 如下:
任何动态的执行SQL 都有注入的风险,因为动态意味着不重用执行计划,而如果不重用执行计划的话,那么就基本上无法保证你写的SQL所表示的意思就是你要表达的意思。
这就好像小时候的填空题,查找密码是(____) 并且用户名是(____)的用户。
不管你填的是什么值,我所表达的就是这个意思。
最后再总结一句:因为参数化查询可以重用执行计划,并且如果重用执行计划的话,SQL所要表达的语义就不会变化,所以就可以防止SQL注入,如果不能重用执行计划,就有可能出现SQL注入, 存储过程也是一样的道理,因为可以重用执行计划。
参数化查询为什么能够防止SQL注入 (转)的更多相关文章
- 参数化查询为什么能够防止SQL注入
原文地址: http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html http://zhangxugg-163-com.itey ...
- 为什么预处理和参数化查询可以防止sql注入呢?
在传统的写法中,sql查询语句在程序中拼接,防注入(加斜杠)是在php中处理的,然后就发语句发送到mysql中,mysql其实没有太好的办法对传进来的语句判断哪些是正常的,哪些是恶意的,所以直接查询的 ...
- EntityFramework Core 2.0执行原始查询如何防止SQL注入?
前言 接下来一段时间我们来讲讲EntityFramework Core基础,精简的内容,深入浅出,希望为想学习EntityFramework Core的童鞋提供一点帮助. EntityFramewor ...
- EF Core 2.0 执行原始查询如何防止SQL注入
using (var context = new EFCoreDbContext()) { var searchString = "Jeffcky Wang"; Formattab ...
- 【挖洞经验】如何在一条UPDATE查询中实现SQL注入
直奔主题 跟往常一样,在喝完我最爱的果汁饮料之后,我会习惯性地登录我的Synack账号,然后选择一个应用来进行渗透测试,此时我的“黑客之夜”便正式开始了. 我与很多其他的安全研究人员的习惯一样,我会在 ...
- 防止sql注入的参数化查询
参数化查询为什么能够防止SQL注入 http://netsecurity.51cto.com/art/201301/377209.htm OleDbDataAdapter Class http://m ...
- SQL注入与参数化查询
SQL注入的本质 SQL注入的实质就是通过SQL拼接字符串追加命令,导致SQL的语义发生了变化.为什么发生了改变呢? 因为没有重用以前的执行计划,而是对注入后的SQL语句重新编译,然后重新执行了语法解 ...
- Sql Server 的参数化查询
为什么要使用参数化查询呢?参数化查询写起来看起来都麻烦,还不如用拼接sql语句来的方便快捷.当然,拼接sql语句执行查询虽然看起来方便简洁,其实不然.远没有参数化查询来的安全和快捷. 今天刚好了解了一 ...
- 如何向非技术人(程序猿)解释SQL注入?
前两天看博客园新闻,有一篇文章名为<我该如何向非技术人解释SQL注入?>(http://kb.cnblogs.com/page/515151/).是一个外国人写的,伯乐在线翻译的.我当时看 ...
随机推荐
- iframe自适应高度,多层嵌套iframe自适应高度的解决方法
在页面无刷新更新方面,虽然现在的ajax很强悍,但是处理代码相对多点.想比之下,iframe就简单多了!处理iframe的自适应宽.高,会经常用到,网上整理了一份,写在这里备用: 单个iframe 高 ...
- C#集合之队列
队列是其元素按照先进先出(FIFO)的方式来处理的集合. 队列使用System.Collections.Generic名称空间中的泛型类Queue<T>实现.在内部,Queue<T& ...
- dp--hdu1171(01背包)
hdu1171 题目 Problem Description Nowadays, we all know that Computer College is the biggest department ...
- 微信内置浏览器H5 弹出键盘 遮盖文本框解决办法 Fixed失效
if(/Android [4-6]/.test(navigator.appVersion)) { window.addEventListener("resize", functio ...
- mysql 查询json字段 json_extract (mysql 5.7及以上)
找第一层: SELECT * FROM tourists WHERE json_data->'$.weixinOpenId' = '299485886686868' 或者 SELECT * FR ...
- js中自执行函数写法
//自执行写法1 (function T(){ alert(1) })() //自执行写法2 var T1=function(){ alert(1) }(); //传值 var para1={a:1; ...
- element-ui日期组件DatePicker选择日期范围赋值编辑问题
最近在项目中使用element-UI的日期范围组件时遇到一个问题,相信很多人也做过这种场景,一个录入页面也同时是编辑页面,编辑的时候就需要先赋值.但是我给date组件赋值后,确无法操作了,change ...
- sql 性能优化相关--总结别人的总结,未做验证,先归纳
sql语句性能达不到你的要求,执行效率让你忍无可忍,一般会时下面几种情况. 网速不给力,不稳定. 服务器内存不够,或者SQL 被分配的内存不够. sql语句设计不合理 没有相应的索引,索引不合理 没有 ...
- 08-oracle统计函数(单组分组函数)
--count时尽量count(列名),count(*)也可以. --count,max,min,sum,avg,median(中位数) select count(empno),count(disti ...
- kafka producer自定义partitioner和consumer多线程
为了更好的实现负载均衡和消息的顺序性,Kafka Producer可以通过分发策略发送给指定的Partition.Kafka Java客户端有默认的Partitioner,平均的向目标topic的各个 ...