9

我們擁有大量依賴動態SQL的SQL Server存儲過程。如何清理SQL Server中的動態SQL - 防止SQL注入

存儲過程的參數用於動態SQL語句中。

我們需要在這些存儲過程中使用標準驗證函數來驗證這些參數並防止SQL注入。

假設我們有這些約束:

  1. 我們不能改寫程序不使用動態SQL

  2. 我們不能用等由sp_OACreate,使用正則表達式進行驗證。

  3. 我們不能修改調用存儲過程的應用程序在傳遞給存儲過程之前驗證參數。

是否有一組字符可以過濾掉以確保我們不會受到SQL注入的影響?

+0

ouch。通常它是3)應該被修改以防止SQL注入。請記住,它是「SQL注入」,而不是「SQL拒絕」。一旦它到達DB,它應該已經被清理。但是,如果你說你不能更改應用程序,那麼我想你不能。有興趣看到答案。 – RPM1984 2010-11-04 23:55:15

回答

10

我相信有,你不必擔心三種不同的情況:(這裏的報價是不允許任何東西)'''' + replace(@string, '''', '''''') + ''''

  • 名稱:

    • 字符串(任何需要引號)quotename(@string)
    • 的東西不能被引用:這需要白名單

    一切在一個字符串變量(charvarcharncharnvarchar等),其來自用戶控制源必須使用上述方法中的一個。這意味着,即使你希望成爲數字的東西,如果它們存儲在字符串變量中,它們也會被引用。

    有關詳細信息,請參閱 Microsoft Magazine (過時鏈接:2016年10月19日)

    下面是使用這三種方法的一個例子:

    EXEC 'SELECT * FROM Employee WHERE Salary > ''' + 
        REPLACE(@salary, '''', '''''') + -- replacing quotes even for numeric data 
        ''' ORDER BY ' + QUOTENAME(@sort_col) + ' ' + -- quoting a name 
        CASE @sort_dir WHEN 'DESC' THEN 'DESC' END  -- whitelisting 
    

    還要注意,通過在線完成所有的字符串操作在EXEC聲明沒有與截斷問題無關。如果將中間結果分配給變量,則必須必須確保變量足夠大以保存結果。如果您使用SET @result = QUOTENAME(@name),則應定義@result以保存至少258(2 * 128 + 2)個字符。如果您使用SET @result = REPLACE(@str, '''', ''''''),則應該將@result定義爲@str(假設@str中的每個字符都可能是引用)的兩倍。當然,保存最終SQL語句的字符串變量必須足夠大以容納所有靜態SQL和所有結果變量。

  • +0

    我同意這裏,它完全取決於什麼SQL正在構建 – 2010-11-05 01:40:12

    2

    OWASP有關於此策略的一些信息。它應該永遠是最後一搏的選項(如我鏈接到文章中解釋),但如果這是你唯一的選擇......

    http://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet

    約它是一個在最後所文章報價溝選項

    然而,與使用參數化的 查詢相比,此方法是脆弱的 。這種技術應該只是 ,謹慎使用以經濟有效的方式改進原有的 代碼。 從零開始構建的應用程序,或 應用程序需要低風險 寬容應構建或 重寫使用參數化 查詢。

    從本質上講,反對這種方法的論點即使你逃避了所有已知的不良輸入,也不能保證有人不會在未來想出一個避開它的方法。

    但是,爲了回答你的問題具體...

    字符列表逃跑是我連接到上面的文章。

    編輯如前所述,文章沒有提供很好的鏈接。但是,對於SQL Server,這是:http://msdn.microsoft.com/en-us/library/ms161953.aspx

    請注意,您需要轉義的字符列表會因數據庫平臺而異,但它看起來像您使用的是SQL Server,因此這應該是相關的。 。

    濾波輸入也可以是在通過去除轉義字符保護免受SQL注入有所幫助:從下面的文章

    報價。但是,由於可能會造成問題的字符數量很大,因此這不是可靠的防禦措施。以下示例搜索字符串分隔符。

    private string SafeSqlLiteral(string inputSQL) 
    { 
        return inputSQL.Replace("'", "''"); 
    } 
    

    like子句

    請注意,如果您使用的是LIKE子句,通配符仍然必須進行轉義:

    s = s.Replace("[", "[[]"); 
    s = s.Replace("%", "[%]"); 
    s = s.Replace("_", "[_]"); 
    
    +1

    -1:該文章沒有說明MS SQL Server要逃脫什麼字符。它只是鏈接到另一篇文章,並沒有明確哪些字符可以逃脫。 – Gabe 2010-11-05 01:21:41

    +0

    你說得對。編輯我的答案。 – David 2010-11-05 02:15:07

    3

    這是一個非常討厭的問題,其問題你想解決,但這裏是一個微不足道的案件,(審查,請讓我知道,如果我錯過了一個案件,這與NO保證)

    create proc Bad 
        @param nvarchar(500) 
    as 
    
    exec (N'select ''' + @param + N'''') 
    
    go 
    
    -- oops injected 
    exec Bad 'help'' select ''0wned!'' select ''' 
    
    go 
    
    create proc NotAsBad 
        @param nvarchar(500) 
    as 
    
    declare @safish nvarchar(1000), @sql nvarchar(2000) 
    set @safish = replace(@param, '''', '''''') 
    
    set @sql = N'select ''' + @safish + N'''' 
    
    exec (@sql) 
    
    go 
    
    -- this kind of works, but I have not tested everything 
    exec NotAsBad 'help'' select ''0wned!'' select ''' 
    
    +0

    +1,我從來沒有看到任何暗示這不起作用的東西。 – Gabe 2010-11-05 01:15:03

    +1

    在我看來,使用除sp_executesql之外的所有值作爲參數傳遞的任何動態SQL都只是純粹的弊端。 – 2010-11-05 13:00:39

    +0

    仍然脆弱。假設NotAsBad的主體包含以下內容:set @sql = N'select * from'+ @ safish ....如果用戶可以猜出他們可以提交的表的名字@param ='tablename; drop database xyz; - ' – frankadelic 2010-11-05 21:52:22

    6

    瑣碎的情況下,可以通過QUOTENAME固定和REPLACE:

    set @sql = N'SELECT ' + QUOTENAME(@column) + 
        N' FROM Table WHERE Name = ' + REPLACE(@name, '''', ''''''); 
    

    雖然QUOTENAME可以在文字中,也添加了單引號和替換單引號與雙單引號,因爲它截斷輸入不建議使用128個字符。

    但這只是冰山一角。有多部分名稱(dbo.table)需要注意:引用多部分名稱會導致無效標識符[dbo.table],它必須被解析並拆分(使用PARSENAME),然後正確引用到[dbo].[table]

    另一個問題是截斷攻擊,即使您在文字上進行了簡單的REPLACE操作,也可能發生截斷攻擊,請參見New SQL Truncation Attacks And How To Avoid Them

    SQL注入的問題永遠無法解決每個過程中放置​​一個魔術功能。這就像問'我想要一個能讓我的代碼運行得更快的函數'。防止注入攻擊是和端到端遊戲,需要編碼紀律一路通過,它不能簡單地添加爲一個事後。您最好的機會是檢查每一個程序,並分析T-SQL代碼逐行,着眼於漏洞,然後解決發現問題。

    +0

    我建議*不*使用'PARSENAME',因爲它的目的是用於已經引用的名稱。如果你的用戶告訴你他想從'secret..table'獲取數據,你想查詢'[secret..table]'並且得到一個錯誤。你不希望他能夠查詢'[secret] .. [table]'! – Gabe 2010-11-05 04:35:55

    +0

    在我看來,使用除sp_executesql以外的所有值作爲參數傳遞的任何動態SQL都只是純粹的弊端。 – 2010-11-05 13:00:03

    0

    您能否獲得SQL CLR可以很有用 - 您至少可以使用它來編寫比使用T-SQL更有效的消毒方法。在一個perfact世界中,你可以用參數化語句和其他更強大的結構完全替換存儲過程。

    +0

    不幸的是,由於數據庫管理員的限制,我無法使用CLR – frankadelic 2010-11-05 16:25:48

    4

    有了這些制約因素,

    這裏有可能給你一些方向上的兩個選項:

    1. 使用白名單識別/分析器只接受有能力的格式,並與預期的關鍵字和表的查詢。這可能只適用於非常好的SQL解析器,它可以真正理解語法。

    2. 在受限制的環境中執行查詢。例如,使用權限非常有限的用戶帳戶。例如,只允許(讀取)訪問某些視圖,這些視圖永遠不會返回敏感數據,並且不允許訪問所有其他視圖,所有存儲過程,函數和表。更安全的是在另一臺數據庫服務器上執行這些查詢。另外不要忘記禁用OPENROWSET命令。

    請注意以下事項:

    1. 當您接受,除了那些有無效的關鍵字的所有查詢,你肯定會失敗,因爲黑色的房源總是失敗。尤其是對於像SQL這樣複雜的語言。

    2. 不要忘記,即使在使用這些提示時,允許來自您不能信任的源的動態SQL也是最邪惡的,因爲偶爾會發現bugs可通過發送特製SQL來濫用到服務器。因此,即使您應用這些提示,風險仍然存在。

    3. 當您決定使用允許動態SQL的解決方案時。請不要以爲你可以自己提出一個安全的解決方案,特別是如果你想保護敏感的業務數據。僱用數據庫服務器安全專家來幫助你。

    -1

    還有一個辦法是可能可能的工作,雖然這取決於被允許在存儲過程的參數是什麼人物。不要轉義可用於SQL注入的麻煩字符,而應刪除字符。例如,如果你有這個SP:

    create procedure dbo.MYSP(@p1 varchar(100)) 
    as begin 
        set @p1 = Replace(@p1, '''',' '); -- Convert single quotes to spaces 
        set @p1 = Replace(@p1, ';', ' '); 
        set @p1 = Replace(@p1, '--', ' ');  
        set @p1 = Replace(@p1, '/*', ' ');  
        set @p1 = Replace(@p1, '*/', ' ');  
        set @p1 = Replace(@p1, 'xp_', ' ');  
        ... 
    end; 
    

    你可以用空格或空字符串替換任何單引號。這種方法也可以用來替換註釋字符,如/ * */- 通過使用更多的替換命令(正如我剛纔所示)。但是請注意,只有在正常輸入中不要期望這些字符,這種方法纔會起作用,這取決於您的應用程序。

    注組替換的字符是基於https://msdn.microsoft.com/en-us/library/ms161953(SQL.105).aspx

    +0

    SQL注入不稱爲「單引用注入」。因爲某種原因。 – 2017-10-25 09:16:30

    +0

    我對'單引號注入'不熟悉,我剛剛描述的技術是防範SQL注入的一種方法,它基於上面引用的Microsoft文章。我不清楚你爲什麼低估了這個答案。 – Ubercoder 2017-10-25 09:33:26

    +0

    然後讓自己熟悉安全基礎知識,並瞭解爲什麼任何基於黑名單的方法故意有缺陷 – 2017-10-25 09:35:33

    2

    有一組我們可以過濾掉,以確保我們不容易受到SQL注入的字符?

    NO

    SQL注入不叫「某些字符集合注射液」,並且是有原因的。過濾掉某些字符可能會使特定的漏洞複雜化,但不會阻止SQL注入本身。要利用SQL注入,必須編寫SQL。而SQL不限於少數特殊字符。