1. 程式人生 > >引數化查詢為什麼能夠防止SQL注入

引數化查詢為什麼能夠防止SQL注入

轉自:http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html 

很多人都知道SQL注入,也知道SQL引數化查詢可以防止SQL注入,可為什麼能防止注入卻並不是很多人都知道的。

本文主要講述的是這個問題,也許你在部分文章中看到過這塊內容,當然了看看也無妨。

首先:我們要了解SQL收到一個指令後所做的事情:

在這裡,我簡單的表示為: 收到指令 -> 編譯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

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

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 ('[email protected]+')')    
        comm.CommandText = "exec('select * from Users(nolock) where UserID in ('[email protected]+')')";
        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 (''[email protected]+'')'')',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 (''[email protected]+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4); delete from Users;--'

不要以為加了個@UserID 就代表能夠防止SQL注入,實際執行的SQL 如下:

任何動態的執行SQL 都有注入的風險,因為動態意味著不重用執行計劃,而如果不重用執行計劃的話,那麼就基本上無法保證你寫的SQL所表示的意思就是你要表達的意思。
這就好像小時候的填空題,查詢密碼是(____) 並且使用者名稱是(____)的使用者。
不管你填的是什麼值,我所表達的就是這個意思。
最後再總結一句:因為引數化查詢可以重用執行計劃,並且如果重用執行計劃的話,SQL所要表達的語義就不會變化,所以就可以防止SQL注入,如果不能重用執行計劃,就有可能出現SQL注入,
儲存過程也是一樣的道理,因為可以重用執行計劃。

相關推薦

引數查詢為什麼能夠防止SQL注入

轉自:http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html  很多人都知道SQL注入,也知道SQL引數化查詢可以防止SQL注入,可為什麼能防止注入卻並不是很多人都知道的。 本文主要講述的是這個問題,也許你在部分文章中看到過這塊內容,

nodejs中查詢mysql防止SQL注入

Performing queries The most basic way to perform a query is to call the .query() method on an object (like a Connection, Pool, or PoolNamespace inst

玩轉JDBC打造資料庫操作萬能工具類JDBCUtil,加入了高效的資料庫連線池,利用了引數繫結有效防止SQL注入

SELECT * FROM emp_test 成功查詢到了14行資料 第1行:{DEPT_TEST_ID=10, EMP_ID=1001, SALARY=10000, HIRE_DATE=2010-01-12, BONUS=2000, MANAGER=1005, JOB=Manager, NAME=張無忌}

SQL或HQL預編譯語句,能夠防止SQL注入,但是不能處理%和_特殊字元

最近專案在做整改,將所有DAO層的直接拼接SQL字串的程式碼,轉換成使用預編譯語句的方式。個人通過寫dao層的單元測試,有以下幾點收穫。 dao層程式碼如下 //使用了預編譯sql public Li

sql注入防止SQL注入引數處理

sql注入的兩種情況: 操作程式碼: import pymysql user = input('使用者名稱: ').strip() pwd = input('密碼: ').strip() # 連結 conn = pymysql.connect(host='localhost', user='ro

C#引數(防止SQL注入)

/* * C#防止SQL注入式攻擊 * Author:ICE FROG * TIME:2016/4/20 */ /* * SQL注入式攻擊就是值通過SQL執行語句的漏洞進行百分百匹

MySQL— pymysql模組(防止sql注入),視覺軟體Navicat

一.Pymysql import pymysql #python2.X 中是 mysqldb 和 pythonmysql 用法是一模一樣的 #pymysql可以偽裝成上面這兩個模組 user = input('username: ') pwd = input('passwo

mybatis模糊查詢防止sql注入

SQL注入,大家都不陌生,是一種常見的攻擊方式。攻擊者在介面的表單資訊或URL上輸入一些奇怪的SQL片段(例如“or ‘1’=’1’”這樣的語句),有可能入侵引數檢驗不足的應用程式。所以,在我們的應用中需要做一些工作,來防備這樣的攻擊方式。在一些安全性要求很高的應用中(比如銀行軟體),經常使用將SQ

AOP實踐--ASP.NET MVC 5使用Filter過濾Action引數防止sql注入,讓你程式碼安全簡潔

在開發程式的過程中,稍微不注意就會隱含有sql注入的危險。今天我就來說下,ASP.NET mvc 5使用Filter過濾Action引數防止sql注入,讓你程式碼安全簡潔。不用每下地方對引數的值都進行檢查,看是使用者輸入的內容是否有危險的sql。如果沒個地方都要加有幾個缺

【Jmeter】——sql引數查詢測試

前言   現在大家寫的sql語句基本都是引數化的   當然在jmeter中也是可以測試引數化的sql語句   但是我始終都是有些疑問   不知道為什麼要測引數化的,我感覺沒什麼區別呢   

sql引數查詢

//在ASP.NET程式中使用引數化查詢 //ASP.NET環境下的查詢化查詢也是通過Connection物件和Command物件完成。如果資料庫是SQL Server,就可以用有名字的引數了,格式是“@”字元加上引數名。 SqlConnection conn = new SqlCon

Mybatis like 查詢 防止SQL注入方法相關原理和解決方法整理

SQL注入:引自百度百科: 所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字串,最終達到欺騙伺服器執行惡意的SQL命令。具體來說,它是利用現有應用程式,將(惡意)的

淺析Sql Server引數查詢

錯誤認識1.不需要防止sql注入的地方無需引數化   引數化查詢就是為了防止SQL注入用的,其它還有什麼用途不知道、也不關心,原則上是能不用引數就不用引數,為啥?多麻煩,我只是做公司內部系統不用擔心SQL注入風險,使用引數化查詢不是給自己找麻煩,簡簡單單拼SQL,萬事OK 錯誤認識2.引數化查詢時是否指定

Mabatis中模糊查詢防止sql注入

#{xxx},使用的是PreparedStatement,會有型別轉換,所以比較安全; ${xxx},使用字串拼接,可以SQL注入; like查詢不小心會有漏洞,正確寫法如下: Mysql:   select * from user where name like

【PHP】使用引數繫結防止SQL注入

<html> <head> <title>Sql注入演示</title> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <

命名引數防止sql注入,使用named parameter

使用named parameter String hql = "from BuildHibernate p where p.beimId= :beimId  ";         Query query = getSession().createQuery(hql);   

wireshark抓包分析mybatis的sql引數查詢

  我們使用jdbc操作資料庫的時候,都習慣性地使用引數化的sql與資料庫互動。因為引數化的sql有兩大有點,其一,防止sql注入;其二,提高sql的執行效能(同一個connection共用一個的sql編譯結果)。下面我們就通過mybatis來分析一下引數化sql的過程,以及和非引數化sql的不同。   注意

Mybatis防止sql注入原理

     SQL 注入是一種程式碼注入技術,用於攻擊資料驅動的應用,惡意的SQL 語句被插入到執行的實體欄位中(例如,為了轉儲資料庫內容給攻擊者)。[摘自]  SQL注入 - 維基百科SQL注入,大家都不陌生,是一種常見的攻擊方式。攻擊者在介面的表單資訊或UR

iBatis解決自動防止sql注入

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

sql注入防止sql注入

資料庫中的資料 sql程式碼 package com.zjw.jdbc2; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.