1. 程式人生 > >SQL 中單引號 和一些特殊字元的處理

SQL 中單引號 和一些特殊字元的處理

為了防止程式SQL語句錯誤以及SQL注入,單引號必須經過處理。有2種辦法:

1、使用引數,比如SELECT * FROM yourTable WHERE name = @name;

在JAVA中就是用預處理PreparedStatement來新增引數。

2、如果不用引數,而用字串拼接的話,單引號必須經過判斷並替換,在資料庫中,用2個單引號代表1個實際的單引號。所以,如果是拼接方式,需要用String.Replace("’", "”")來替換一下,將1個單引號替換為2個就沒有問題了。

一. 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之類的擴充套件儲存過程,獲取系統資訊,甚至控制、破壞系統。 
六、結論 
為了防止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; 
}