程序員們寫代碼的時候講究TDD(測試驅(qū)動開發(fā)):在實現(xiàn)一個功能前,會先寫一個測試用例,然后再編寫代碼使之運行通過。其實當黑客SQL Injection時,同樣是一個TDD的過程:他們會先嘗試著讓程序報錯,然后一點一點的修正參數(shù)內(nèi)容,當程序再次運行成功之時,注入也就隨之成功了。
進攻:
假設(shè)你的程序里有類似下面內(nèi)容的腳本:
$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']參數(shù),所以必然會報錯,可能會是類似下面的信息:
supplied argument is not a valid MySQL result resource in ...
這些信息就足以說明腳本存在漏洞了,我們可以再耍點手段:
/articles.php?id=0 union select 1,2,3
之所以select 1,2,3是因為union要求兩邊的字段數(shù)一致,前面是id,title,content三個字段,后面1,2,3也是三個,所以不會報語法錯誤,還有設(shè)置id=0是一條不存在的記錄,那么查詢的結(jié)果就是1,2,3,反映到網(wǎng)頁上,原本顯示id的地方會顯示1,顯示title的地方會顯示2,顯示content的地方會顯示3。
至于如何繼續(xù)利用,還要看magic_quotes_gpc的設(shè)置:
當magic_quotes_gpc為off時:
/articles.php?id=0 union select 1,2,load_file('/etc/passwd')
如此一來,/etc/passwd文件的內(nèi)容就會顯示在原本顯示content的地方。
當magic_quotes_gpc為on時:
此時如果直接使用load_file('/etc/passwd')就無效了,因為單引號被轉(zhuǎn)義了,但是還有辦法:
/articles.php?id=0 union select 1,2,load_file(char(47,101,116,99,47,112,97,115,115,119,100))
其中的數(shù)字就是/etc/passwd字符串的ASCII:字符串每個字符循環(huán)輸出ord(...)
除此以為,還可以使用字符串的十六進制:字符串每個字符循環(huán)輸出dechex(ord(...))
/articles.php?id=0 union select 1,2,load_file(0x2f6574632f706173737764)
這里僅僅說了數(shù)字型參數(shù)的幾種攻擊手段,屬于冰山一角,字符串型參數(shù)等攻擊手段看后面的文檔鏈接。
防守:
網(wǎng)絡(luò)上有一些類似SQL Injection Firewall的軟件可供使用,比如說GreenSQL,如果網(wǎng)站已經(jīng)開始遭受SQL Injection攻擊,那么使用這樣的快捷工具往往會救你一命,不過這樣的軟件在架構(gòu)上屬于一個Proxy的角色,多半會影響網(wǎng)站并發(fā)性能,所以在選擇與否這個問題上最好視客觀條件來慎重決定。很多時候?qū)I(yè)的軟件并不是必須的,還有很多輕量級解決方案,下面演示一下如何使用awk來檢測可能的漏洞。
創(chuàng)建detect_sql_injection.awk腳本,內(nèi)容如下(如果要拷貝一下內(nèi)容的話記得不要包括行號):
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,有兩種調(diào)用方法:
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
現(xiàn)實環(huán)境中有很多應(yīng)用這個腳本的方法,比如說通過CRON定期掃描程序源文件,或者在SVN提交時通過鉤子方法自動匹配。
使用專業(yè)工具也好,檢測腳本亦罷,都是被動的防守,問題的根本始終取決于在程序員頭腦里是否有必要的安全意識,下面是一些必須要牢記的準則:
1:數(shù)字型參數(shù)使用類似intval,floatval這樣的方法強制過濾。
2:字符串型參數(shù)使用類似mysql_real_escape_string這樣的方法強制過濾,而不是簡單的addslashes。
3:最好拋棄mysql_query這樣的拼接SQL查詢方式,盡可能使用PDO的prepare綁定方式。
4:使用rewrite技術(shù)隱藏真實腳本及參數(shù)的信息,通過rewrite正則也能過濾可疑的參數(shù)。
5:關(guān)閉錯誤提示,不給攻擊者提供敏感信息:display_errors=off。
6:以日志的方式記錄錯誤信息:log_errors=on和error_log=filename,定期排查,Web日志最好也查。
7:不要用具有FILE權(quán)限的賬號(比如root)連接MySQL,這樣就屏蔽了load_file等危險函數(shù)。
8:......
網(wǎng)站安全其實并不復(fù)雜,總結(jié)出來就是一句話:過濾輸入,轉(zhuǎn)義輸出。其中,我們上面一直討論的SQL Injection問題就屬于過濾輸入問題,至于轉(zhuǎn)義輸出問題,其代表是Cross-site scripting,但它不屬于本文的范疇,就不多說了。