c# - statements - sql injection解決方法




避免不帶參數的SQL注入 (14)

在我們的代碼中,我們正在進行另一個關於使用參數化SQL查詢的討論。 我們有兩方面的討論:我和其他一些人說,我們應該總是使用參數來防止sql注入和其他人認為這是不必要的。 相反,他們希望用所有字符串中的兩個撇號替換單撇號以避免sql注入。 我們的數據庫都運行Sql Server 2005或2008,我們的代碼庫運行在.NET框架2.0上。

讓我在C#中給你一個簡單的例子:

我希望我們使用這個:

string sql = "SELECT * FROM Users WHERE [email protected]";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe

而其他人想要這樣做:

string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?

SafeDBString函數的定義如下:

string SafeDBString(string inputValue) 
{
    return "'" + inputValue.Replace("'", "''") + "'";
}

現在,只要我們對查詢中的所有字符串值使用SafeDBString,我們就應該是安全的。 對?

有兩個理由使用SafeDBString函數。 首先,它是從石器時代開始已經完成的方式,其次,由於您看到在數據庫上運行的excact查詢,因此更容易調試sql語句。

那麼。 我的問題是,是否真的足夠使用SafeDBString函數來避免SQL注入攻擊。 我一直在試圖找到打破此安全措施的代碼示例,但我找不到任何示例。

有沒有人可以打破這個? 你會怎麼做?

編輯:總結到目前為止的回复:

  • 沒有人找到解決Sql Server 2005或2008中的SafeDBString的方法。 這很好,我想?
  • 幾個回復指出,使用參數化查詢時可以獲得性能提升。 原因是查詢計劃可以重用。
  • 我們也同意使用參數化查詢可以提供更易讀的代碼,更易於維護
  • 此外,總是使用參數比使用各種版本的SafeDBString,字符串到數字的轉換和字符串到日期的轉換更容易。
  • 使用參數可以實現自動類型轉換,這在我們使用日期或十進制數時非常有用。
  • 最後: 不要像JulianR寫的那樣嘗試自己做安全 。 數據庫供應商花費大量時間和金錢安全。 我們沒有辦法做得更好,沒有理由我們應該嘗試去做好自己的工作。

所以,雖然沒有人能夠打破SafeDBString函數的簡單安全性,但我還是得到了很多其他好的論點。 謝謝!


2年後 ,我重新開始......任何發現參數很痛的人都歡迎嘗試我的VS擴展, QueryFirst 。 您可以在真實的.sql文件(驗證,智能感知)中編輯您的請求。 要添加參數,只需將它直接輸入到SQL中,並以'@'開頭。 當你保存文件時,QueryFirst將生成包裝類,讓你運行查詢並訪問結果。 它將查找參數的數據庫類型並將其映射到.net類型,您會發現它將作為生成的Execute()方法的輸入。 不能更簡單 。 以正確的方式進行操作比以任何其他方式更快更容易,創建sql注入漏洞變得不可能,或者至少是非常困難。 還有其他殺手級的優勢,比如能夠刪除數據庫中的列,並立即看到應用程序中的編譯錯誤。

合法免責聲明:我寫了QueryFirst


以下是使用參數化查詢的幾個原因:

  1. 安全性 - 數據庫訪問層知道如何刪除或轉義數據中不允許的項目。
  2. 分離關注點 - 我的代碼不負責將數據轉換為數據庫喜歡的格式。
  3. 沒有冗餘 - 我不需要在執行此數據庫格式化/轉義的每個項目中包含程序集或類; 它內置於類庫中。

如果不使用參數,您無法輕鬆進行任何類型的用戶輸入檢查。

如果您使用SQLCommand和SQLParameter類來進行數據庫調用,仍然可以看到正在執行的SQL查詢。 查看SQLCommand的CommandText屬性。

當參數化查詢非常容易使用時,我一直都希望通過roll-your-own方法防止SQL注入。 其次,僅僅因為“它總是這樣做”並不意味著它是正確的做法。


如果你保證你會傳遞一個字符串,這是唯一安全的。

如果你沒有在某個點傳遞字符串呢? 如果只傳遞一個數字會怎樣?

http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB

最終會成為:

SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB

對安全問題同意很大。
使用參數的另一個原因是效率。

數據庫將始終編譯您的查詢並對其進行緩存,然後重新使用緩存的查詢(對於後續請求顯然更快)。 如果使用參數,則即使使用不同的參數,數據庫也會在綁定參數之前根據SQL字符串重新使用緩存查詢。

但是,如果您不綁定參數,則SQL字符串會在每個請求(具有不同參數)上發生更改,並且它永遠不會匹配緩存中的內容。


從我不得不調查SQL注入問題的很短時間內,我可以看到,將值設置為“安全”也意味著您正在關閉通向數據實際需要撇號的情況的大門 - 關於某人的姓名,例如O'Reilly。

這留下參數和存儲過程。

是的,你應該總是試著以你現在知道的最好的方式實現代碼 - 而不僅僅是它總是如何完成。


我會使用存儲過程或函數來處理所有事情,所以這個問題不會出現。

我必須將SQL放入代碼中,我使用參數,這是唯一有意義的參數。 提醒持異議者:黑客比他們更聰明,並且有更好的動機來打破那些試圖超越他們的代碼。 使用參數,這根本不可能,而且不是很難。


我沒有看到任何其他答案解決'為什麼自己做得不好'這一方面,但考慮SQL截斷攻擊

還有QUOTENAME T-SQL函數,如果你不能說服他們使用參數,它可能會有幫助。 它捕獲了很多(所有?)逃脫的qoute問題。


所以我會說:

1)你為什麼試圖重新實現內置的東西? 它在那裡,隨時可用,易於使用並已在全球範圍內進行調試。 如果在它中發現未來的錯誤,它們將會被修復並迅速向所有人提供,而無需您執行任何操作。

2)有哪些流程可以保證不會錯過對SafeDBString的調用? 只在一個地方丟失它可能會引發一系列問題。 你有多少注意這些事情,並考慮當接受的正確答案很容易達到時,這種努力會浪費多少。

3)你有多確定你覆蓋了微軟(數據庫和訪問庫的作者)在你的SafeDBString實現中知道的每個攻擊向量......

4)讀取sql的結構有多容易? 該示例使用+串聯,參數非常類似string.Format,它更具可讀性。

另外,有兩種方法可以計算實際運行的內容 - 滾動您自己的LogCommand函數,一個沒有安全擔憂的簡單函數,甚至可以查看sql跟踪來確定數據庫認為是真的在發生什麼。

我們的LogCommand功能很簡單:

    string LogCommand(SqlCommand cmd)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(cmd.CommandText);
        foreach (SqlParameter param in cmd.Parameters)
        {
            sb.Append(param.ToString());
            sb.Append(" = \"");
            sb.Append(param.Value.ToString());
            sb.AppendLine("\"");
        }
        return sb.ToString();
    }

對或錯,它給我們提供了我們需要的信息而沒有安全問題。


有幾個漏洞(我不記得它是哪個數據庫)與SQL語句的緩衝區溢出有關。

我想說的是,SQL注入更多的是“逃避報價”,你不知道接下來會發生什麼。


由於已經給出的原因,參數是一個非常好的主意。 但是我們討厭使用它們,因為創建參數並將其名稱分配給變量供以後在查詢中使用時會出現三重間接頭部殘骸。

以下類包裝了您通常用於構建SQL請求的stringbuilder。 它允許您編寫參數化查詢,而無需創建參數 ,因此您可以專注於SQL。 你的代碼看起來像這樣...

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId, SqlDbType.Int);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName, SqlDbType.NVarChar);
myCommand.CommandText = bldr.ToString();

代碼的可讀性,我希望你的同意,大大改善了,輸出是一個適當的參數化查詢。

班級看起來像這樣...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace myNamespace
{
    /// <summary>
    /// Pour le confort et le bonheur, cette classe remplace StringBuilder pour la construction
    /// des requêtes SQL, avec l'avantage qu'elle gère la création des paramètres via la méthode
    /// Value().
    /// </summary>
    public class SqlBuilder
    {
        private StringBuilder _rq;
        private SqlCommand _cmd;
        private int _seq;
        public SqlBuilder(SqlCommand cmd)
        {
            _rq = new StringBuilder();
            _cmd = cmd;
            _seq = 0;
        }
        //Les autres surcharges de StringBuilder peuvent être implémenté ici de la même façon, au besoin.
        public SqlBuilder Append(String str)
        {
            _rq.Append(str);
            return this;
        }
        /// <summary>
        /// Ajoute une valeur runtime à la requête, via un paramètre.
        /// </summary>
        /// <param name="value">La valeur à renseigner dans la requête</param>
        /// <param name="type">Le DBType à utiliser pour la création du paramètre. Se référer au type de la colonne cible.</param>
        public SqlBuilder Value(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append(paramName);
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this;
        }
        public SqlBuilder FuzzyValue(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append("'%' + " + paramName + " + '%'");
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this; 
        }

        public override string ToString()
        {
            return _rq.ToString();
        }
    }
}

盡可能使用參數化查詢。 有時,即使不使用任何奇怪字符的簡單輸入,如果它未被識別為數據庫中字段的輸入,也可以創建SQL注入。

所以,只需讓數據庫完成識別輸入本身的工作,更不用說當您需要實際插入奇怪的字符時,它也會節省大量的麻煩,否則這些字符會被轉義或更改。 它甚至可以節省一些寶貴的運行時間,因為無需計算輸入。


這裡有幾篇文章可以幫助你說服你的同事。

http://www.sommarskog.se/dynamic_sql.html

http://unixwiz.net/techtips/sql-injection.html

就個人而言,我寧願永遠不要讓任何動態代碼觸及我的數據庫,要求所有聯繫人都是通過sps(而不是使用動態SQl的)。 這意味著沒有什麼能夠讓我允許用戶執行的操作可以完成,並且內部用戶(除非有少數人擁有用於管理目的的生產訪問權限)不能直接訪問我的表並創建破壞性數據,竊取數據或進行欺詐。 如果您運行財務應用程序,這是最安全的方法。


通過參數化查詢,您不僅可以防止sql注入。 您還可以獲得更好的執行計劃緩存潛力。 如果你使用sql server查詢分析器,你仍然可以看到“在數據庫上運行的確切的sql”,所以你在調試sql語句方面也沒有失去任何東西。







sql-injection