1. 程式人生 > >SQL的注入式攻擊方式和避免方法

SQL的注入式攻擊方式和避免方法

SQL 注入是一種攻擊方式,在這種攻擊方式中,惡意程式碼被插入到字串中,然後將該字串傳遞到 SQL Server 的例項以進行分析和執行。任何構成 SQL 語句的過程都應進行注入漏洞檢查,因為 SQL Server 將執行其接收到的所有語法有效的查詢。

SQL 注入的主要形式包括直接將程式碼插入到與 SQL 命令串聯在一起並使其得以執行的使用者輸入變數。一種間接的攻擊會將惡意程式碼注入要在表中儲存或作為元資料儲存的字串。在儲存的字串隨後串連到一個動態 SQL 命令中時,將執行該惡意程式碼。

注入過程的工作方式是提前終止文字字串,然後追加一個新的命令。由於插入的命令可能在執行前追加其他字串,因此攻擊者將用註釋標記“--”來終止注入的字串。執行時,此後的文字將被忽略。

以下指令碼顯示了一個簡單的 SQL 注入。此指令碼通過串聯硬編碼字串和使用者輸入的字串而生成一個 SQL 查詢:

var Shipcity = Request.form. ("ShipCity");
var sql = "select * from OrdersTable where ShipCity = '" + ShipCity + "'";使用者將被提示輸入一個市縣名稱。如果使用者輸入 Redmond,則查詢將由與下面內容相似的指令碼組成:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond'

但是,假定使用者輸入以下內容:Redmond'; drop table OrdersTable--

此時,指令碼將組成以下查詢:SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';drop table OrdersTable--'

分號 (;) 表示一個查詢的結束和另一個查詢的開始。雙連字元 (--) 指示當前行餘下的部分是一個註釋,應該忽略。如果修改後的程式碼語法正確,則伺服器將執行該程式碼。SQL Server 處理該語句時,SQL Server 將首先選擇 OrdersTable 中的所有記錄(其中 ShipCity 為 Redmond)。然後,SQL Server 將刪除 OrdersTable。

只要注入的 SQL 程式碼語法正確,便無法採用程式設計方式來檢測篡改。

因此,必須驗證所有使用者輸入,並仔細檢查在您所用的伺服器中執行構造 SQL 命令的程式碼。本主題中的以下各部分說明了編寫程式碼的最佳做法。

驗證所有輸入
始終通過測試型別、長度、格式和範圍來驗證使用者輸入。實現對惡意輸入的預防時,請注意應用程式的體系結構和部署方案。請注意,設計為在安全環境中執行的程式可能會被複制到不安全的環境中。以下建議應被視為最佳做法:

對應用程式接收的資料不做任何有關大小、型別或內容的假設。例如,您應該進行以下評估:

如果一個使用者在需要郵政編碼的位置無意中或惡意地輸入了一個 10 MB 的 MPEG 檔案,應用程式會做出什麼反應?

如果在文字欄位中嵌入了一個 DROP TABLE 語句,應用程式會做出什麼反應?

測試輸入的大小和資料型別,強制執行適當的限制。這有助於防止有意造成的緩衝區溢位。

測試字串變數的內容,只接受所需的值。拒絕包含二進位制資料、轉義序列和註釋字元的輸入內容。這有助於防止指令碼注入,防止某些緩衝區溢位攻擊。

使用 XML 文件時,根據資料的架構對輸入的所有資料進行驗證。

絕不直接使用使用者輸入內容來生成 Transact-SQL 語句。

使用儲存過程來驗證使用者輸入。

在多層環境中,所有資料都應該在驗證之後才允許進入可信區域。未通過驗證過程的資料應被拒絕,並向前一層返回一個錯誤。

實現多層驗證。對無目的的惡意使用者採取的預防措施對堅定的攻擊者可能無效。更好的做法是在使用者介面和所有跨信任邊界的後續點上驗證輸入。
例如,在客戶端應用程式中驗證資料可以防止簡單的指令碼注入。但是,如果下一層認為其輸入已通過驗證,則任何可以繞過客戶端的惡意使用者就可以不受限制地訪問系統。

絕不串聯未驗證的使用者輸入。字串串聯是指令碼注入的主要輸入點。

在可能據以構造檔名的欄位中,不接受下列字串:AUX、CLOCK$、COM1 到 COM8、CON、CONFIG$、LPT1 到 LPT8、NUL 以及 PRN。

如果可能,拒絕包含以下字元的輸入。

輸入字元    在 Transact-SQL 中的含義 
       ;                查詢分隔符。 
       '           字元資料字串分隔符。 
      --               註釋分隔符。 
  /* ... */      註釋分隔符。伺服器不對 /* 和 */ 之間的註釋進行處理。 
      xp_         用於目錄擴充套件儲存過程的名稱的開頭,如 xp_cmdshell。
使用型別安全的 SQL 引數
SQL Server 中的 Parameters 集合提供了型別檢查和長度驗證。如果使用 Parameters 集合,則輸入將被視為文字值而不是可執行程式碼。使用 Parameters 集合的另一個好處是可以強制執行型別和長度檢查。範圍以外的值將觸發異常。以下程式碼段顯示瞭如何使用 Parameters 集合:

SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id",
     SqlDbType.VarChar, 11);
parm.Value = Login.Text;在此示例中,@au_id 引數被視為文字值而不是可執行程式碼。將對此值進行型別和長度檢查。如果 @au_id 值不符合指定的型別和長度約束,則將引發異常。

在儲存過程中使用引數化輸入
儲存過程如果使用未篩選的輸入,則可能容易受 SQL Injection 攻擊。例如,以下程式碼容易受到攻擊:

SqlDataAdapter myCommand =
new SqlDataAdapter("LoginStoredProcedure '" +
                               Login.Text + "'", conn);如果使用儲存過程,則應使用引數作為儲存過程的輸入。

在動態 SQL 中使用引數集合
如果不能使用儲存過程,您仍可使用引數,如以下程式碼示例所示:

SqlDataAdapter myCommand = new SqlDataAdapter(
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn);
SQLParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
Parm.Value = Login.Text;

篩選輸入
篩選輸入可以刪除轉義符,這也可能有助於防止 SQL 注入。但由於可引起問題的字元數量很大,因此這並不是一種可靠的防護方法。以下示例可搜尋字串分隔符。

private string SafeSqlLiteral(string inputSQL)
{
  return inputSQL.Replace("'", "''");
}LIKE 子句
請注意,如果要使用 LIKE 子句,還必須對萬用字元字元進行轉義

s = s.Replace("[", "[[]");
s = s.Replace("%", "[%]");
s = s.Replace("_", "[_]"); 在程式碼中檢查 SQL 注入應檢查所有呼叫 EXECUTE、EXEC 或 sp_executesql 的程式碼。可以使用類似如下的查詢來幫助您標識包含這些語句的過程。此查詢檢查單詞 EXECUTE 或 EXEC 後是否存在 1 個、2 個、3 個或 4 個空格。

SELECT object_Name(id) FROM syscomments

WHERE UPPER(text) LIKE '%EXECUTE  (%'

OR UPPER(text) LIKE '%EXECUTE  (%'

OR UPPER(text) LIKE '%EXECUTE   (%'

OR UPPER(text) LIKE '%EXECUTE    (%'

OR UPPER(text) LIKE '%EXEC (%'

OR UPPER(text) LIKE '%EXEC  (%'

OR UPPER(text) LIKE '%EXEC   (%'

OR UPPER(text) LIKE '%EXEC    (%'

OR UPPER(text) LIKE '%SP_EXECUTESQL%'

使用 QUOTENAME() 和 REPLACE() 包裝引數
在選擇的每個儲存過程中,驗證是否對動態 Transact-SQL 中使用的所有變數都進行了正確處理。來自儲存過程的輸入引數的資料或從表中讀取的資料應包裝在 QUOTENAME() 或 REPLACE() 中。請記住,傳遞給 QUOTENAME() 的 @variable 值的資料型別為 sysname,且最大長度為 128 個字元。

    @variable                         建議的包裝 
安全物件的名稱              QUOTENAME(@variable) 
字串 ≤ 128 個字元      QUOTENAME(@variable, '''') 
字串 > 128 個字元      REPLACE(@variable,'''', '''''')
使用此方法時,可對 SET 語句進行如下修改:

--Before:

SET @temp = N'select * from authors where au_lname='''

+ @au_lname + N''''

--After:

SET @temp = N'select * from authors where au_lname='''

+ REPLACE(@au_lname,'''','''''') + N''''

由資料截斷啟用的注入
如果分配給變數的任何動態 Transact-SQL 比為該變數分配的緩衝區大,那麼它將被截斷。如果攻擊者能夠通過將意外長度的字串傳遞給儲存過程來強制執行語句截斷,則該攻擊者可以操作該結果。

通過向 128 個字元的緩衝區傳遞 154 個字元,攻擊者便可以在不知道舊密碼的情況下為 sa 設定新密碼。

EXEC sp_MySetPassword 'sa', 'dummy', '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''

因此,應對命令變數使用較大的緩衝區,或直接在 EXECUTE 語句內執行動態 Transact-SQL。

使用 QUOTENAME(@variable, '''') 和 REPLACE() 時的截斷
如果 QUOTENAME() 和 REPLACE() 返回的字串超過了分配的空間,該字串將被自動截斷。以下示例中建立的儲存過程顯示了可能出現的情況。

當您串聯 sysname 型別的值時,應使用足夠大的臨時變數來儲存每個值的最多 128 個字元。應儘可能直接在動態 Transact-SQL 內呼叫 QUOTENAME()。