海水正藍

          面朝大海,春暖花開
          posts - 145, comments - 29, trackbacks - 0, articles - 1
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          【轉】參數化查詢為什么能夠防止SQL注入

          Posted on 2013-01-16 22:09 小胡子 閱讀(255) 評論(0)  編輯  收藏 所屬分類: SQL Server

          很多人都知道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]

          3F3ECD42B7A24B139ECA0A7D584CA195

           

          插入一些數據:

          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注入無關的主題。

           

           

          可以看到執行結果:

          15C19A6170754E21A52A79AAA01B9B48

          這個是SQL profile 跟蹤的SQL 語句。

          5CB6FB63846740C494C6466FE27D2B3C

           

          注入的代碼如下:

          select COUNT(*) from Users where Password = 'a' and UserName = 'b' or 1=1—'

          這里有人將UserName設置為了 “b' or 1=1 –”.

           

          實際執行的SQL就變成了如下:

          782A96FEE0784A39B5500CAE267B90EE

           

          5A8FCD361FFE414AB18AEE5C9ED681DE

            可以很明顯的看到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;
           

          18EFAED775BF4DB9A36C57B39EC6913D

           

          博客園有篇文章: Sql Server參數化查詢之where in和like實現詳解

           

          在這篇文章中有這么一段:

          image

           

          這里作者有一句話:”不過這種寫法和直接拼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'
          D25E99E053D549AF955518AD0A320259
           
          可以看到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 如下:

           

          3C50EFE68418448496BAC7773067AB6F
           
          任何動態的執行SQL 都有注入的風險,因為動態意味著不重用執行計劃,而如果不重用執行計劃的話,那么就基本上無法保證你寫的SQL所表示的意思就是你要表達的意思。
           
          這就好像小時候的填空題,查找密碼是(____) 并且用戶名是(____)的用戶。
          不管你填的是什么值,我所表達的就是這個意思。
           
          最后再總結一句:因為參數化查詢可以重用執行計劃,并且如果重用執行計劃的話,SQL所要表達的語義就不會變化,所以就可以防止SQL注入,如果不能重用執行計劃,就有可能出現SQL注入,
          存儲過程也是一樣的道理,因為可以重用執行計劃。
          原文出自:
          http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 定远县| 方城县| 乐至县| 天峨县| 砚山县| 公安县| 绥滨县| 邯郸县| 黎平县| 芷江| 柳州市| 台北市| 湖口县| 梁平县| 班戈县| 凯里市| 临湘市| 天门市| 南澳县| 广德县| 西青区| 略阳县| 武川县| 黄陵县| 八宿县| 新蔡县| 霍邱县| 大厂| 聂荣县| 绥棱县| 云龙县| 康平县| 岚皋县| 桦甸市| 澄城县| 乌兰察布市| 灵璧县| 河南省| 喜德县| 常州市| 乐陵市|