根據紅日安全寫的文章,學習PHP代碼審計的第四節內容,題目均來自PHP SECURITY CALENDAR 2017,講完題目會用一個實例來加深鞏固,這是以前寫的,有興趣能夠去看看:
PHP代碼審計01之in_array()函數缺陷
PHP代碼審計02之filter_var()函數缺陷
PHP代碼審計03之實例化任意對象漏洞php
如今我們看第一題,代碼以下:html
<?php class Login { public function __construct($user, $pass) { $this->loginViaXml($user, $pass); } public function loginViaXml($user, $pass) { if ( (!strpos($user, '<') || !strpos($user, '>')) && (!strpos($pass, '<') || !strpos($pass, '>')) ) { $format = '<?xml version="1.0"?>' . '<user v="%s"/><pass v="%s"/>'; $xml = sprintf($format, $user, $pass); $xmlElement = new SimpleXMLElement($xml); // Perform the actual login. $this->login($xmlElement); } } } new Login($_POST['username'], $_POST['password']); ?>
咱們來看上面的代碼,看第1214行,這裏經過格式話字符串的方式,使用XML結構來存儲用戶的登錄信息,這樣很容易形成注入,再看上面的代碼,最後一行實例化了這個類,17行調用了login來進行登錄操做。下面到重點了,看代碼810行,這裏用到了strpos()函數來進行過濾<>符號,這個函數的用法以下:
以下:web
<?php var_dump(strpos("abcd","a")); var_dump(strpos("abcd","y")); ?>
咱們發現,找到子字符串的話就會返回對應的下標,沒找到會返回false。而在PHP中,0和false的取反都是true,這點須要咱們注意,這道題目就是開發者在使用這個函數時,只考慮了返回false的狀況,而沒有考慮當首字符匹配時返回0的狀況。致使了過濾被繞過從而實施XML攻擊。
如今知道了如何繞過,那麼構造payload:user=<"><injected-tag%20property="&pass=<injected-tag>
爲了更好的理解,來看一下構造這個payload時,strpos()函數的返回結果以下:shell
$user='<"><injected-tag property="'; $pass='<injected-tag>'; var_dump(strpos($user,'<')); var_dump(!strpos($user,'<')); var_dump(strpos($user,'>')); var_dump(!strpos($user,'>'));
是否是理解了一些呢,如今看上面的代碼,思考一下若是咱們使用這個payload會返回什麼呢。數據庫
var_dump((!strpos($user, '<') || !strpos($user, '>')) && (!strpos($pass, '<') || !strpos($pass, '>')));
返回了true,其實就是var_dump((true || false) &&(true || false))
成功繞過,能夠進行XML注入。
經過上面的學習講解是否是對strpos()函數了解更深一些了呢?下面我們看一個實例,這個實例也是設計者沒有考慮周全,致使任意用戶密碼重置。安全
此次的實例是DeDecms V5.7SP2正式版,源碼能夠從網上下載搭建,這個很容易找到,就不詳細說了,上面說了,這個漏洞是任意用戶密碼重置,下面咱們來分析代碼,漏洞的觸發點在 member/resetpassword.php 文件中,是由於對接收的參數safeanswer沒有進行嚴格的類型判斷致使被繞過。下面看代碼:
如今來對上面的代碼作一個分析,當$dopost==safequestion時,經過$mid對應的id值來查詢當前用戶的safequestion,safeanswer,userid,email等值,如今來看第84行,這裏的意思是當咱們傳入的安全問題和安全答案等於以前設置的值時,就傳入sn()函數,重點來了,注意看,這裏用的是雙等於來驗證,而沒有用三等於,因此,這裏是能夠被繞過的。當用戶沒有設置安全問題時,那麼默認狀況安全問題值爲0,安全答案值爲null,這裏指的是數據庫中的值,而咱們若是傳入空值時,那麼就是空字符串,84行語句也就變成了if('0' == '' && null == ''),也就是if(false&&true),因此咱們只須要讓前半部分轉爲true就能夠了,經過測試以下圖,均可以和0比較等於true。函數
if("0"=="0.0"){echo "成功1"."\n";} if("0"=="0e1"){echo "成功2"."\n";} if("0"=="0e12"){echo "成功3"."\n";} if("0"=="0e123"){echo "成功4"."\n";} if("0"=="0."){echo "成功5"."\n";}
上面的幾種payload均能使得 safequestion 爲 true,成功進入sn()函數
咱們跟進sn()函數,代碼以下:
在sn()內部,會根據id到pwd_tmp表中判斷是否存在對應的臨時密碼記錄,根據結果肯定分支,走向 newmail 函數。如今咱們假設第一次來進行忘記密碼操做,那麼如今的$row的值應該爲空,也就會進入if(!is_array($row))分支,而後在newmail()函數中執行INSERT操做,具體代碼在這個文件的上面,如圖:
這個代碼的功能是發送郵件到相關郵箱,並插入一條記錄到dede_pwd_tmp表中,漏洞觸發點在這裏,咱們如今看92~95行。若是 ($send == 'N') 這個條件爲真,那就跳轉到修改頁,經過 ShowMsg 打印出修改密碼功能的連接。拼接的url爲:
http://www.dmsj.com/DedeCMS-V5.7-UTF8-SP2/uploads/member/resetpassword.php?dopost=getpasswd&id=$mid&key=$randval
如今我們跟進dopost=getpasswd去看看,在member/resetpassword.php文件中,代碼以下:
post
這裏用empty()函數來判斷$id和$row是否爲空,若是不爲空的話,就繼續向下走,進入if(empty($setp))中,先判斷是否超時,若是沒超時的話進入修改頁面,我圈起來了,如今跟過去看一下具體代碼以下:
發現數據包中 $setp=2,因此代碼功能又回到了member/resetpassword.php文件中,以下:
分析上面代碼,咱們發現若是傳入的$key和數據庫中的$row['pwd']相同時,則完成密碼的重置,也完成了攻擊的分析過程。學習
如今註冊兩個帳號,分別是test123和test456,查看mid的值以下,test456的mid爲3。test123的mid爲2。
我如今登錄test456帳戶,訪問我們構造的payload。來修改test123的密碼。
http://www.dmsj.com/DedeCMS-V5.7-UTF8-SP2/uploads/member/resetpassword.php?dopost=safequestion&safequestion=0e1&safeanswer=&id=2
我如今登錄的是test456帳戶,訪問url抓包。
獲取到了key值,而後來訪問修改密碼的url:
http://www.dmsj.com/DedeCMS-V5.7-UTF8-SP2/uploads/member/resetpassword.php?dopost=getpasswd&id=2&key=xy8UzeOI
咱們發現到了修改密碼的頁面,直接能夠修改密碼。咱們將密碼修改成abcdef,而後登錄test123,發現登錄成功,密碼成功被修改。
測試
經過這篇文章的學習與講解,是否是對strpos()函數和PHP弱類型繞過有了必定的瞭解了呢?下一篇文章會對escapeshellarg與escapeshellcmd使用不當來進行學習與講解,一塊兒努力吧!