PHP弱類型漏洞復現
在一次滲透測試中,由于甲方維護做的比較好,并沒有發現什么漏洞,在即將結束的時候,我還是沒有大的突破。
于是我又進行了一波仔細的信息搜集,發現系統中存在phpList,頓時,我眼前一亮——我記得以前看到過phplist的相關漏洞。
隨即我開始搜索phplist的歷史漏洞,經過我的一通操作,最終確定phplist的版本為3.5.0,漏洞為弱類型漏洞,利用弱類型漏洞登錄進去,之后就一切順利了。
由于在客戶機器上做的滲透,截圖神馬的都沒有······下面我在本機上搭建了一個環境,對phplist3.5.0弱類型漏洞進行一下復現。
1、復現過程
phpList 3.5.0版本存在的安全漏洞編號是CVE-2020-5847,該漏洞源于程序沒有正確處理開頭為0e之后全部為數字字符的哈希值。遠程攻擊者可利用該漏洞繞過管理員賬戶的身份驗證。
首先下載phpList 3.5.0-RC1,然后解壓找到這個目錄:

將這個目錄拷貝到網站目錄下,并重命名。
修改config/config.php配置文件,設置要連接的數據庫和賬戶密碼:

同時建立phplistdb數據庫。
訪問http://127.0.0.1/phplist/admin/;
接下來初始化安裝,設置管理員的賬號密碼:

這里的密碼要設置成 sha256 后以0e開頭的字符串,如TyNOQHUS。
我們再次訪問后臺,以密碼 34250003024812進行登錄,其sha256后也是以0e開頭。
“TyNOQHUS——hash :
0e66298694359207596086558843543959518835691168370379069085300385”
“34250003024812——hash:
0e46289032038065916139621039085883773413820991920706299695051332”

登錄成功,漏洞驗證成功:

下面還是具體分析一下漏洞處的代碼。
驗證管理員登錄的PHP文件為:
“phpListAdminAuthentication.php”
登錄時,密碼傳入其中,經過if判斷后,可以看到$encryptedPass(密碼sha256后的值)是使用==來判斷和數據庫中的值是否一樣,即$encryptedPass == $passwordDB。
PHP弱類型比較,就會造成0exxxxx == 0eyyyyy(會把每一個以”0e”開頭的哈希值都解釋為0),具體代碼如下圖:

2、什么是PHP弱類型
首先先說一下,什么是強類型和弱類型?
語言通常被分為強類型和弱類型兩種。強類型指的是強制數據類型的語言,換句話說,一個變量一旦被定義了成某個類型,如果不經過強制類型轉換,這個變量就一直是這個類型。
在變量使用之前,必須聲明變量的類型和名稱,且不經強制轉換,不允許兩種不同類型的變量互相操作。而弱類型可以隨意轉換變量的類型。
PHP作為最受歡迎的開源腳本語言,越來越多的應用于Web開發領域。同時PHP屬于弱類型語言,即定義變量的時候不用聲明它是什么類型。
弱類型確實給程序員書寫代碼帶來了很大的便利,但是在安全領域,特性既漏洞,這些特性在代碼里面經常就是漏洞最容易出現的地方。
PHP官方也給出了類型比較表,表格顯示了 PHP 類型和比較運算符在松散和嚴格比較時的作用。
松散比較:

嚴格比較:

總結一下PHP弱類型產生漏洞的原理:
PHP在處理哈希字符串時,會利用”!=”或”==”來對哈希值進行比較,它把每一個以”0e”開頭的哈希值都解釋為0,所以如果兩個不同的密碼經過哈希以后,其哈希值都是以”0e”開頭的,那么PHP將會認為他們相同,都是0。- 當不同類型的變量進行比較的時候就會存在變量轉換的問題,在轉換之后就有可能會存在弱類型問題。例如需要將GET或者是POST的參數轉換為int類型,或者是兩個變量不匹配的時候,PHP會自動地進行變量轉換。但是PHP是一個弱類型的語言,導致在進行類型轉換的時候就會產生弱類型相關的漏洞。
- 函數轉換出錯時導致的弱類型問題,例如:strcmp函數參數str1不為預期的String類型時(例如數組,),在PHP 5.3版本之前將返回-1,5.3之后的版本將返回NULL。
通過以上方式可以產生密碼重置、繞過管理員賬戶的身份驗證、注入、cookie偽造等弱類型漏洞。一些CTF試題也會利用其弱類型進行設置一些如MD5碰撞、十六進制轉換等問題。
下面通過一些漏洞實例來進行說明。
3、CVE-2014-0166(WordPress Cookie偽造)
在wordpress-3.8.2的補丁中有以下代碼:

而在wordpress-3.8.1的相對應的PHP代碼是這樣的:

通過對比我們自然把全部的關注點放到!=與!==上來。
再看php manual中給出的例子:
var_dump(0 == "a"); // 0 == 0 -> true
var_dump("1" == "01"); // 1 == 1 -> true
var_dump("10" == "1e1"); // 10 == 10 -> true
var_dump(100 == "1e2"); // 100 == 100 -> true
?>
字符串在與數字比較前會自動轉換為數字,所以0=="a"了。
回到wordpress的驗證代碼上來。先生成根據用戶名($username)、密碼($pass_frag)、cookie有效期 ($expiration)、wp-config.php中的key($key)四個信息計算出對應的$hash, 然后用cookie中取得的$hmac值與之進行比較($hmac != $hash ),從而驗證cookie有效性。
cookie的格式是這樣的:
wordpress_hashofurl=username|expiration|hmac
我們能控制的變量有$username和$expiration,其中$username需要固定。于是我們可以通過控制cookie中的$expiration去改變$hash的值,然后將cookie中的$hmac設置為0。
只要不斷改變$expiration,找到滿足$hash=="0"的$hash,就成功偽造了有效的cookie。
4、HDwiki sql注入
在/control/list.php中,代碼如下:

分析代碼可知,從GET里獲得$doctype,即$doctype = $this->get[2],接著進入一個switch語句。我們看到后面直接將$doctype帶入SQL語句了:
$count=$this->db->fetch_total('focus',"type=$doctype");
只要這個switch語句不影響$doctype的值,后面就能注入了。
我們看到case 2和case 3的結果都不會改變$doctype的值,但如果進入default是會將$doctype改為1。
這里就犯了一個“弱類型”的錯誤,當一個字符串和一個數字比較的時候,是會先將字符串強制類型轉換后再與數字比較。
所以我傳入doctype是2xxxx的時候,實際上是會進入case 2而不是default。出了switch語句后,doctype的值還是2xxxx,而不是2。
所以之后的:
$count=$this->db->fetch_total('focus',"type=$doctype");
$doctype帶入SQL語句造成注入。
5CTF中的利用
《MD5碰撞》:
if (isset($_GET['Username']) && isset($_GET['password'])) {
$logined = true;
$Username = $_GET['Username'];
$password = $_GET['password'];
if (!ctype_alpha($Username)) {$logined = false;}
if (!is_numeric($password) ) {$logined = false;}
if (md5($Username) != md5($password)) {$logined = false;}
if ($logined){
echo "successful";
}else{
echo "login failed!";
}
}
?>
這個題目的意思是,輸入一個數字和一個字符串,并且讓他們的MD5值相同,才可以得到successful。
0e在比較的時候會將其視作為科學計數法,所以無論0e后面是什么,0的多少次方還是0。
所以我們只需要輸入一個數字和字符串,進行MD5加密之后都為0e,即可得出答案。
md5('240610708') == md5('QNKCDZO')
成功繞過。
《起名字真難》:
function noother_says_correct($number)
{
$one = ord('1');
$nine = ord('9');
for ($i = 0; $i < strlen($number); $i++)
{
$digit = ord($number{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
return false;
}
}
return $number == '54975581388';
}
$flag='*******';
if(noother_says_correct($_GET['key']))
echo $flag;
else
echo 'access denied';
?>
題目大致的意思就是,輸入一串key,key不可以是數字的形式,但是卻要求與數字54975581388相等,才可以拿到flag。
看完題目就知道要求字符串和數字進行比較,所以:
$number == '54975581388';
看到這就想到了弱類型,54975581388與之匹配的十六進制的字符串是0xccccccccc。
全不是數字,自然就繞過了,得到flag。
6、結語
看到這里相信大家都對PHP弱類型比較了解了,當然還有很多其他的漏洞實例,這里就不一一列舉了。
下面列舉一些以0e開頭的字符串的md5的hash值:
s214587387a:
0e848240448830537924465865611904;
s1502113478a:
0e861580163291561247404381396064;
s1091221200a:
0e940624217856561557816327384675;
s1665632922a:
0e731198061491163073197128363787;
s1885207154a:
0e509367213418206700842008763514;
s1836677006a:
0e481036490867661113260034900752;
s1665632922a:
0e731198061491163073197128363787;
s878926199a:
0e545993274517709034328855841020;
QLTHNDT:
0e405967825401955372549139051580;
QNKCDZO:
0e830400451993494058024219903391;
EEIZDOI:
0e782601363539291779881938479162;
TUFEPMC:
0e839407194569345277863905212547;
UTIPEZQ:
0e382098788231234954670291303879;
UYXFLOI:
0e552539585246568817348686838809;
IHKFRNS:
0e256160682445802696926137988570;
240610708:
0e462097431906509019562988736854;
314282422:
0e990995504821699494520356953734;
571579406:
0e972379832854295224118025748221;
903251147:
0e174510503823932942361353209384;
1110242161:
0e435874558488625891324861198103;
1320830526:
0e912095958985483346995414060832;
1586264293:
0e622743671155995737639662718498;
感謝閱讀,下次再見!
文章來源:仙橋六號部隊