一. SQL Injection及其防範的基本知識
可能你們都知道,SQL注入主要是利用字符型參數輸入的檢查漏洞。
好比說,程序中有這樣的查詢:
string sql = "SELECT * FROM SiteUsers WHERE UserName=" + userName + "";
其中的userName參數是從用戶界面上輸入的。
若是是正常的輸入,好比"Peter",SQL語句會串接成:
"SELECT * FROM SiteUsers WHERE UserName=Peter";
若是攻擊者輸入的是下面的字符串:
"xxx; DROP TABLE SiteUsers WHERE 1=1 or UserName=xxx"
此時SQL語句會變成下面這個樣子:
"SELECT * FROM SiteUsers WHERE UserName=xxx; DROP TABLE SiteUsers WHERE 1=1 or UserName=xxx";
其結果,獲得執行的是兩個SQL語句,第二個語句的後果就比較嚴重了。
防止注入的方法其實很簡單,只要把用戶輸入的單引號變成雙份就好了:
string sql = "SELECT * FROM SiteUsers WHERE UserName=" + userName.Replace("","") + "";
這樣,若是輸入的是上面那種惡意參數,整個SQL語句會變成:
"SELECT * FROM SiteUsers WHERE UserName=xxx; DROP TABLE SiteUsers WHERE 1=1 or UserName=xxx";
被執行的仍是一個SQL語句,整個粗體部分都成爲參數值。
通常的作法,是在程序中統一調用下面這樣的共通函數,對參數進行處理:
private string SafeSqlLiteral(string inputSQL)
{
return inputSQL.Replace("", "");
}
因爲不少人會疏忽這種單引號替換,因此真正安全的作法是使用參數化查詢。
二. 參數化查詢
在ADO.NET中,提供了一種參數化查詢方法,能夠替代上面這種拼接SQL語句的作法。
參數化查詢的具體實現是:
(1)組織一個夾帶參數名的SQL語句,做爲SqlCommand的CommandText。
(2)使用Parameters.Add方法設置參數值。
(3)執行SqlCommand。(這個步驟跟上面那種拼接SQL的辦法是同樣的。)
下面是一個例子:
string sql = "SELECT T2.dep_code, T2.dep_name FROM DEP ";
sql += " WHERE T2.dep_name like (%+ @Param + %) ";
SqlCommand sqlCommand = new SqlCommand(sql,cn);
sqlCommand.Parameters.Add(new SqlParameter("Param", s));
其中的@Param就是參數名,s則是用戶輸入的查詢條件字串。
(順便注:Oracle查詢語句參數用問號表示,不是"@參數名"的形式。)
使用這種參數化查詢的辦法,防止SQL注入的任務就交給ADO.Net了。
若是在項目中統一規定必須使用參數化查詢,就不用擔憂因個別程序員的疏忽致使的SQL注入漏洞了。
可是,問題尚未完,SQL注入的漏洞是堵住了,可是查詢結果的正確性,參數化查詢並不能幫上什麼忙。
三. 通配符問題
若是使用LIKE語句進行模糊查詢,會有一些特殊的通配符問題。
SQL Server的通配符包括下劃線(_)和百分號(%),分別表示單個字符和任意多字符。
若是用戶輸入參數中包括這些通配符,就會出現結果不正確的問題。
好比說:
WHERE T2.name like (%+ @Param + %)
若是用戶輸入下劃線,他期待的結果應該是name字段值含有下劃線的記錄,可是結果是全部記錄都會被查詢出來。輸入百分號也是如此。
爲此,在將用戶輸入的內容做爲參數值傳入以前,必須進行通配符的轉義處理(英文叫作Escape),也就是說,若是用戶輸入的查詢條件中含有通配符,必須將這些字符做爲數據而不是通配符來對待。
在SQL Server的查詢語句中,將通配符轉義爲普通數據的方法是用方括號括起來。
好比說,若是想要查詢帶有下劃線的字段,正確的寫法是:
WHERE T2.name like (%+ [_] + %)
一樣,若是想要查詢帶有百分號的字段,正確的寫法是:
WHERE T2.name like (%+ [%] + %)
因此,即便使用參數化查詢,也必須在將用戶輸入的內容看成參數值傳入SqlCommand.Parameters以前,先進行下面的處理:
s = s.Replace("%", "[%]");
s = s.Replace("_", "[_]");
四. 方括號問題
若是你足夠細心,可能發現了還有一個方括號問題。
既然方括號是用來界定數據內容的,那麼若是用戶輸入的查詢參數自己就包括方括號時,會出現什麼結果呢?
根據用戶的指望,若是輸入一個方括號,查詢結果中應該只包括那些字段值中含有方括號的記錄。
可是實驗結果代表,若是是沒有配成對的單個左方括號,查詢時這個左方括號會被忽略。
也就是說,下面這個語句:
WHERE T2.name like (%+ [ + %)
等價於下面這個語句:
WHERE T2.name like (%+ + %)
這將致使查詢結果中包含表中的所有記錄,就像沒有任何過濾條件同樣。
爲此,若是用戶輸入的查詢條件中含有左方括號的話,還必須對左方括號進行轉義:
s = s.Replace("[", "[[]");
注:右方括號沒有這個問題。
五. 其餘注意事項
按照微軟的建議,凡有可能致使問題的輸入,能夠在UI部分就進行檢查並拒掉。
這些可疑輸入包括:
分號(;):多個查詢語句之間的分隔符,注入攻擊時的惡意查詢語句每每就是第二個查詢語句。
單引號():字符串數據分隔符,這是最危險的,前面已經討論了。
註釋符(–或者/*,*/):有些數據庫能夠利用註釋設置一些查詢引擎的行爲,好比如何利用索引等。
xp_:擴展存儲過程的前綴,SQL注入攻擊得手以後,攻擊者每每會經過執行xp_cmdshell之類的擴展存儲過程,獲取系統信息,甚至控制、破壞系統。
6、結論
爲了防止SQL注入,同時避免用戶輸入特殊字符時查詢結果不許確的問題,應該作兩件事:
(1)使用參數化查詢。
(2)在使用用戶輸入的字符串數據設置查詢參數值以前,首先調用下面的共通處理函數:
private static string ConvertSql(string sql)
{
//sql = sql.Replace("'", "''"); // ADO.NET已經作了,不要本身作
sql = sql.Replace("[", "[[]"); // 這句話必定要在下面兩個語句以前,不然做爲轉義符的方括號會被看成數據被再次處理
sql = sql.Replace("_", "[_]");
sql = sql.Replace("%", "[%]");
return sql;
}程序員