程序員們寫代碼的時候講究TDD(測試驅動開發):在實現一個功能前,會先寫一個測試用例,然後再編寫代碼使之運行通過。其實當黑客SQL Injection時,同樣是一個TDD的過程:他們會先嘗試著讓程序報錯,然後一點一點的修正參數內容,當程序再次運行成功之時,注入也就隨之成功了。
進攻:假設你的程序裡有類似下面內容的腳本:
$sql = "SELECT id, title, content FROM articles WHERE id = {$_GET['id']}";
正常訪問時其URL如下:
/articles.PHP?id=123
當黑客想判斷是否存在SQL Injection漏洞時,最常用的方式就是在整形ID後面加個單引號:
/articles.PHP?id=123'
由於我們沒有過濾$_GET['id']參數,所以必然會報錯,可能會是類似下面的信息:
supplIEd argument is not a valid MySQL result resource in ...
這些信息就足以說明腳本存在漏洞了,我們可以再耍點手段:
/articles.PHP?id=0 union select 1,2,3
之所以select 1,2,3是因為union要求兩邊的字段數一致,前面是id,title,content三個字段,後面1,2,3也是三個,所以不會報語法錯誤,還有設置id=0是一條不存在的記錄,那麼查詢的結果就是1,2,3,反映到網頁上,原本顯示id的地方會顯示1,顯示title的地方會顯示2,顯示content的地方會顯示3。
至於如何繼續利用,還要看magic_quotes_gpc的設置:
當magic_quotes_gpc為off時:
/articles.PHP?id=0 union select 1,2,load_file('/etc/passwd')
如此一來,/etc/passwd文件的內容就會顯示在原本顯示content的地方。
當magic_quotes_gpc為on時:
此時如果直接使用load_file('/etc/passwd')就無效了,因為單引號被轉義了,但是還有辦法:
/articles.PHP?id=0 union select 1,2,load_file(char(47,101,116,99,47,112,97,115,115,119,100))
其中的數字就是/etc/passwd字符串的ASCII:字符串每個字符循環輸出ord(...)
除此以為,還可以使用字符串的十六進制:字符串每個字符循環輸出dechex(ord(...))
/articles.PHP?id=0 union select 1,2,load_file(0x2f6574632f706173737764)
這裡僅僅說了數字型參數的幾種攻擊手段,屬於冰山一角,字符串型參數等攻擊手段看後面的文檔鏈接。
防守:網絡上有一些類似SQL Injection Firewall的軟件可供使用,比如說
GreenSQL,如果網站已經開始遭受SQL Injection攻擊,那麼使用這樣的快捷工具往往會救你一命,不過這樣的軟件在架構上屬於一個Proxy的角色,多半會影響網站並發性能,所以在選擇與否這個問題上最好視客觀條件來慎重決定。很多時候專業的軟件並不是必須的,還有很多輕量級解決方案,下面演示一下如何使用awk來檢測可能的漏洞。
創建detect_sql_injection.awk腳本,內容如下(如果要拷貝一下內容的話記得不要包括行號):
01 #!/bin/gawk -f
02
03 /\$_(GET|POST|COOKIE|REQUEST)\s*\[/ {
04 IGNORECASE = 1
05 if (match($0, /\$.*(sql|query)/)) {
06 IGNORECASE = 0
07 output()
08 next
09 }
10 }
11
12 function output()
13 {
14 $1 = $1
15 print "CRUD: " $0 "\nFILE: " FILENAME "\nLINE: " FNR "\n"
16 }此腳本可匹配出類似如下的問題代碼,想要擴展匹配模式也容易,只要照貓畫虎寫if match語句即可。
1:$sql = "SELECT * FROM users WHERE username = '{$_POST['username']}'";
2:$res = MySQL_query("SELECT * FROM users WHERE username = '{$_POST['username']}'");使用前別忘了先chmod +x detect_sql_injection.awk,有兩種調用方法:
1:./detect_sql_injection.awk /path/to/PHP/script/file
2:find /path/to/php/script/directory -name "*.PHP" | xargs ./detect_sql_injection.awk會把有問題的代碼信息顯示出來,樣子如下:
CRUD: $sql = "SELECT * FROM users WHERE username = '{$_POST['username']}'";
FILE: /path/to/file.PHP
LINE: 123現實環境中有很多應用這個腳本的方法,比如說通過CRON定期掃描程序源文件,或者在SVN提交時通過鉤子方法自動匹配。
使用專業工具也好,檢測腳本亦罷,都是被動的防守,問題的根本始終取決於在程序員頭腦裡是否有必要的安全意識,下面是一些必須要牢記的准則:
1:數字型參數使用類似intval,floatval這樣的方法強制過濾。
2:字符串型參數使用類似MySQL_real_escape_string這樣的方法強制過濾,而不是簡單的addslashes。
3:最好拋棄MySQL_query這樣的拼接SQL查詢方式,盡可能使用PDO的
prepare綁定方式。
4:使用rewrite技術隱藏真實腳本及參數的信息,通過rewrite正則也能過濾可疑的參數。
5:關閉錯誤提示,不給攻擊者提供敏感信息:display_errors=off。
6:以日志的方式記錄錯誤信息:log_errors=on和error_log=filename,定期排查,Web日志最好也查。
7:不要用具有FILE權限的賬號(比如root)連接MySQL,這樣就屏蔽了load_file等危險函數。
8:......
網站安全其實並不復雜,總結出來就是一句話:
過濾輸入,轉義輸出。其中,我們上面一直討論的SQL Injection問題就屬於過濾輸入問題,至於轉義輸出問題,其代表是Cross-site scripting,但它不屬於本文的范疇,就不多說了。