作者: 闕榮文 ( querw )
什麼是SQL注入攻擊,有什麼危害
先來看一個例子說說SQL注入攻擊是怎麼回事,有什麼危害.
在有使用者參與的網站中,所有操作中最重要的就是登入.要求使用者輸入使用者名稱和密碼,然後在資料庫裡校正輸入的有效性.
我相信很多人(反正我以前一直就是怎麼寫的)寫類似下面的代碼:
(代碼1)
string strUserName = txtUserName.Text;
string strPwd = txtPwd.Text;
string strSql = "select * from UserTable where username = ";
strSql += "'" + strUserName "'";
strSql += " and password ='" + strPwd + "'";
...
像這樣依賴使用者輸入來"拼湊"SQL語句的代碼是極其脆弱的. 假設一個使用者(也許是駭客)輸入的密碼是 ' or '1'='1 使用者名稱無所謂什麼字元,看看這條SQL語句會變成什麼:
select * from UserTable where username = 'what ever you input' and password = '' or '1'='1'
執行這個語句會返回整個UserTable表,再加上很多網站的管理帳號的使用者名稱就是 admin, 那麼悲劇的發生就無法避免了,入侵者很容易就以管理員的身份登入了,不管你的密碼設定得多長,多複雜. 事實上入侵者能夠利用上述的漏洞擷取到遠遠比你想象的多的資訊,如果他精通SQL的話,整個網站都有可能變成他的玩具. 所謂SQL注入攻擊,大概就是這個意思了.SQL注入攻擊使用正常的WEB瀏覽工具,防火牆對此無能為力,而且現成的攻擊工具很多,使用也簡單.如果網站存在這樣的漏洞的話,任何一個人都有可能輕鬆攻破你的網站.
如何防範?
在網上隨便搜一下,就可以得到 "通用防注入" 代碼,它的原理是檢查訪問者提交的(包括使用GET 和 POST方法)資料,如果內容裡含有某些SQL敏感的單詞,則採取相應的防範措施. 比如某個"通用防注入"的關鍵字字典是: '|exec|insert|select|delete|update|count|chr|truncate|char|declare|--|script|*|char|set|(|) 所有提交的內容中含有上述任何一個單詞,都被認為是攻擊嘗試.
不可否認這是一個解決方案,但我認為這隻是治標不治本,理由:
1. 打擊面過大,比如部落格/新聞網站,使用者的提交包含上面的單詞是很正常的,但是如果過濾的話會被認為是入侵行為.
2. 沒有從根本上解決問題,駭客技術的發展非常快,所謂道高一尺,魔高一丈.這個關鍵字字典也許永遠都沒辦法包含所有危險的關鍵字.
解決問題要找根本,SQL注入漏洞的根源在於直接用訪問者的輸入 "拼湊" SQL語句並執行.那就不要拼湊SQL語句了 - 使用參數或者預存程序. 通過參數或者預存程序,SQL語句受開發人員/管理員控制,使用者的輸入被限制在特定作用範圍,就好像被籠子關著的野獸,牙齒再尖利,也咬不到籠子外的東西.
(代碼2)
string strSql = "select * from UserTable where UserName = @UserName and Password = @Password";
SqlParameter[] param = new SqlParameter[]
{
new SqlParameter("@UserName", strName),
new SqlParameter("@Password", strPwd)
};
DataSet ds = db.OpenDataSetS(strSql, param);
...
現在,不管入侵者提交什麼使用者名稱或者密碼,都被作為 @UserName 和 @Password 的值傳給資料庫引擎,而不在影響SQL語句本身.
另外提一句,適當做一些過濾總是有好處的. 比如這麼一個連結 http://xxxx/userdetail.aspx?userid=100001
伺服器頁面會用Request的QueryString方法(或者類似的方法,本文的寫作背景是ASP.NET / C# / SQL Server 2005)擷取userid的值,如果直接把userid作為SQL語句的一部分,那麼就會造成一個SQL注入漏洞. 但是這裡很明顯,userid是一個數值,如果使用前總是先把字串轉成數值型,那麼就可以避免攻擊.還有很多類似的地方,比如一般都有規定使用者名稱的最長字元數,再接到訪問者輸入時可以檢查一下. 總而言之,在邏輯上做一些檢測會使程式更加嚴謹,錯誤可控.
後記
筆者的網站就經曆過SQL注入攻擊,之前全部採用"拼湊"SQL語句的方式寫代碼,給了我一個慘痛的教訓.網站被安裝shell,重要郵箱被盜(郵箱密碼和網站admin密碼設定成一樣了,另一個慘痛教訓.)
網域名稱被修改.(網域名稱密碼和admin密碼也設定成一樣了,捶胸頓足...) 還好,最後"駭客"同志看在我們都是同胞弟兄的份上,把密碼都換給我了. 被狠狠的上了一課. 感謝他的好心,如果碰到一個有惡意的攻擊者...無法想象.
另外,我在網上搜尋發現一種說法:使用參數並不能絕對杜絕SQL注入攻擊,不過也沒說出個所以然.我也不是很明白,如果您知道,能否告訴我一下(querw@sina.com)?非常感謝.
附錄: 代碼2中OpenDataSetS()函數
public System.Data.DataSet OpenDataSetS(string strSql, SqlParameter[] param)
{
// 判斷連接字串是否為空白.
if (!Open()) return null;
DataSet ds = null;
SqlDataAdapter da = new SqlDataAdapter();
SqlCommand cmd = new SqlCommand();
cmd.Connection = " *** my sql connection string ***";
cmd.CommandType = CommandType.Text;
cmd.CommandText = strSql;
if (param != null)
{
foreach (SqlParameter p in param)
{
cmd.Parameters.Add(p);
}
}
da.SelectCommand = cmd;
ds = new DataSet();
da.Fill(ds);
cmd.Dispose(); cmd = null;
da.Dispose(); da = null;
return ds;
}