簡介
說到PHP弱型別大家肯定不陌生,但是對於PHP中==和===比較時存在的差異,哪一個安全哪一個不安全,肯定很多程式設計師並不清楚。
網路上有很多大佬們的文章都是介紹PHP弱型別原理,及其可能存在的問題理論,還有就是CTF裡面經常出現的場景,然而在具體的實戰當做卻很少有案例或者文章,本文中將我在具體的專案和程式碼審計中遇到的由PHP弱型別引發的漏洞進行一個初步總結,例如由PHP弱型別導致的密碼重置、登入繞過、SQL隱碼攻擊、命令執行、Hash碰撞等漏洞等等,後續遇到新場景繼續補充。
PHP弱型別是什麼
PHP作為最受歡迎的開源指令碼語言,也被稱為是世界上最好的語言,越來越多的應用於Web開發領域。
PHP屬於弱型別語言,即定義變數的時候不用宣告它是什麼型別。作為一個程式設計師,弱型別確實給程式設計師書寫程式碼帶來了很大的便利,這一便利也是PHP語言的一個特性,但是在安全領域,特性既漏洞,這些特性在程式碼裡面經常就是漏洞最容易出現的地方。
PHP比較運算子
在弄明白PHP運算子之前,需要明白PHP變數型別及它們的意義。例如,"42" 是一個字串而 42 是一個整數。FALSE 是一個布林值而 "false" 是一個字串。HTML
表單並不傳遞整數、浮點數或者布林值,它們只傳遞字串。
PHP的比較運算子有==(等於)鬆散比較,它不會去檢查條件式的表示式的型別;===(完全等於)嚴格比較,也就是恆等,它會檢查查表示式的值與型別是否相等,所以這裡面就會引入很多有意思的問題。例如NULL,0,”0”,array()使用==和false比較時,都是會返回true的,而使用===卻不會,如下圖:
在鬆散比較的時候,PHP會將他們的型別進行強制轉換從而統一型別,比如說字元到數字,非bool型別轉換成bool型別。一個數字和一個字串進行比較,PHP會把字串轉換成數字再進行比較。PHP轉換的規則的是:若字串以數字開頭,則取開頭數字作為轉換結果,若無則輸出0。當有一個對比引數是整數的時候,會把另外一個引數強制轉換為整數,如下圖:
PHP官方也給出了型別比較表,表格中顯示了PHP型別和比較運算子在鬆散和嚴格比較時的作用及返回值,應該是比較詳細的,如下圖:
使用PHP函式對變數\$x進行比較:
鬆散比較(==)情況
嚴格比較(===)情況
從上面的表格中已經很清晰的看到==和===比較時的區別了。
實戰中導致的漏洞解析
下面以實戰中遇到的真實場景為例介紹PHP弱型別導致的漏洞。
DedeCMS密碼重置
2018年01月09日,Dedecms官方更新了DedeCMS V5.7
SP2正式版,在此版本及之前版本存在任意使用者密碼重置漏洞,具體導致漏洞的程式碼如圖:
在找回密碼時,當$dopost =safequestion
時,透過傳入的member_id查詢出對應id使用者的安全問題和答案資訊,當我們傳入的問題和答案不為空,並且等於系統設定的問題和答案時就進入sn()函式。
這裡如果使用者設定了問題和答案,我們並不知道問題和答案是什麼,就無法進入sn()函式。但是如果此使用者沒有設定問題和答案呢?此時系統預設問題是”0”,答案是空。
那麼我們傳入答案$safeanswer = “”
時:
$row[‘safeanswer’] == $safeanswer;
成立。
但是傳入問題$safequestion = “0”
時:
empty(\$safequestion)
為真,此時$safequestion = ””
,而$row[safequestion] = “0”
此時$row[safequestion] == $safequestion;
不成立。
所以現在要讓問題相等,才能繞過if判斷,此時如果熟悉PHP的話,會想到PHP的弱型別問題,如下圖:
利用弱型別的特性,==鬆散比較,"0.0"、"0."、"0e123"在empty()函式是不為空,並且鬆散比較"0"時為真,成功進入if條件,進入sn()函式重置密碼。此漏洞在滲透測試和眾測時應該已經被刷很多遍了吧。詳細漏洞分析見:https://mp.weixin.qq.com/s/MTES86qMVDquKrZq2oolVA
ZPanel密碼重置
Sentora / ZPanel 由於設計缺陷,導致存在密碼重置漏洞,具體漏洞程式碼如下:
生成一個隨機的驗證密碼Token,然後更新資料庫,傳送重置密碼連結。
然後點選重置密碼連結後,驗證token有效性,重置密碼。
但是在重置密碼完成後,系統又設定了ac_resethash_tx為空,那麼我們就可以以空的token繼續重置密碼了!!!
具體漏洞細節:https://blogs.securiteam.com/index.php/archives/3386
HDwikiSQL隱碼攻擊
具體漏洞程式碼如圖:
從程式碼中看到,首先從GET裡獲得doctype($doctype =
$this->get[2];)
,然後進入一個switch語句。如果進入case 2和case
3保持\$doctype的值不變,但是進入default將\$doctype改為1,最後看到這句$count=$this->db->fetch_total('focus',"type=$doctype");
,直接將\$doctype帶入SQL語句,所以只要這個switch語句不影響\$doctype的值也就是必須進入case
2和case 3分支,就導致SQL隱碼攻擊漏洞。
如果要進行SQL隱碼攻擊那麼\$doctype必須是字串,但是\$doctype必須進入case 2和case
3分支,case 2和case
3分支又是整型數字,所以就是很經典的弱型別問題進行鬆散比較的點了。
我們只需要將\$doctype的值,也就是GET獲取的引數第一位設定為2或者3就好了,後面跟上sql語句就成功繞過進行sql注入了,如下圖:
如上圖成功繞過進行sql注入。
某產品命令執行
在偶然一次滲透測試專案中,登入到某產品中,發現如下程式碼(圖為虛擬碼):
(圖片丟了...)
從程式碼中看到\$file進入到命令執行,但是\$file中可控的變數是\$config,而且這裡判斷只有\$config==2時才能進入命令執行流程,所以利用弱型別的型別強制轉換就可繞過判斷進入命令執行。
WordPress Cookie偽造
在WordPress 3.8.2的官方補丁中有這樣一個補丁
很明顯,就改變鬆散比較和嚴格比較。
在WordPress中\$hmac來源於cookies,是我們可控的一個輸入引數,結構如下:
\$hash是以下程式碼生成一個md5值,當\$hmac ==
\$hash 時,登入成功。由於是==鬆散比較,所以存在如下幾種情況都可以登入成功:
//第一種情況,完全相等。
\$hmac = ‘1f253e501c301bf5bf293c40d7d92ded’;
\$hash = ‘1f253e501c301bf5bf293c40d7d92ded’;
//第二種情況,第一位為數字,第二位為字母
\$hmac = 1;
\$hash = ‘1f253e501c301bf5bf293c40d7d92ded’;
//第三種情況。第一位為字母
\$hmac = 0;
\$hash = ‘af253e501c301bf5bf293c40d7d92ded’;
//第四種情況,第一位為0,第二位為e,後面所有位為數字
\$hmac = “0”;
\$hash = ‘0e1234567890123456...32’;
由於\$hmac最後獲取的是一個字串,所以很顯然第四種情況是可以成立的。所以最後這個漏洞的利用方式還可以是:讓\$hmac =‘0’,透過生成\$hash,獲得一個,第一位為0,第二位為e,後面所有位為數字的\$hash。
變相資訊洩露
在PHP中進行比較運算時,如果遇到了0e\d+這種字串,就會將這種字串解析為科學計數法,例如0e10,‘e’會識別為次方,0的10次方為0,如下圖所示,兩個不同字串但是在==下他們的md5值相等:
那麼我們來擴充套件一下:
先註冊密碼為240610708的使用者A。
然後用密碼QNKCDZO嘗試登入使用者A。
倘若成功登入,則證明此網站採用了不完備的加密體制md5一次加密。
先註冊密碼為0e33455555的使用者A。
然後用密碼0e66788888嘗試登入使用者A。
倘若成功登入,則證明此網站採用了明文進行儲存密碼!
這樣既可採集目標資訊了,還有其他擴充套件大家自己開腦洞。
其他
除了在PHP的弱型別中存在強制型別轉換外,MySQL中也同樣存在類似的特性,在mysql裡面,當欄位型別為整型,而where語句中的值不為整型的時候,會被轉換成整型才放入查詢。也就是說,如果where
code='xxx'
,xxx不為整型的話,則會先將xxx轉換成整數,才放入查詢,也就是說,如果我們傳入的字串為0aaa,則會轉換成0,再執行。詳見P牛的部落格:
https://www.leavesongs.com/PENETRATION/findpwd-funny-logic-vul.html
參考連結
http://php.net/manual/zh/types.comparisons.php
防護方案
為了避免意想不到的執行效果,根基實際場景應該使用嚴格比較。