PHP 弱類型
PHP 比較 2 個值是否相等可以用 “ == ” 或 “ === ”,“ == ” 會在比較時自動轉換類型而不改變原來的值,因此這個符號經常出現漏洞。“ == ” 比較相等的一些常見值如下,當某些語句的判斷條件是使用 “ == ”來判斷時,就可以使用弱類型來替代。值得一提的是 “0e” 開頭的哈希字符串,PHP 在處理哈希字符串時會把每一個以 “0E” 開頭的哈希值都解釋為 0。
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e12346789' == '0e987654321'
[false] == [0] == [NULL] == ['']
NULL == false == 0
true == 1
如果遇到了 “===” 則不會進行類型轉換,但也並不代表無從下手。如果條件表達式中含有函數,也可以通過傳入數組讓函數返回 NULL 使得條件滿足。
MD5 碰撞漏洞
MD5 信息摘要算法是一種被廣泛使用的密碼散列函數,可以產生出一個 128 位(16字節)的散列值用於確保信息傳輸完整一致。而 PHP 在處理哈希字符串時,“0e” 開頭的值利用 “==” 判斷相等時會被視為 0。所以如果兩個不同的密碼經過哈希以后,其哈希值都是以 ”0E” 開頭的,那么 PHP 將會認為他們相同,這就是所謂的 MD5 碰撞漏洞。如果數據庫中存在這種哈希值以 ”0E” 開頭的密碼的話,就可以以這個用戶的身份輕易地登錄進去。
常用的 MD5 碰撞有:
MD5 | 原值 |
---|---|
0e830400451993494058024219903391 | QNKCDZO |
0e545993274517709034328855841020 | s878926199a |
0e342768416822451524974117254469 | s155964671a |
0e848240448830537924465865611904 | s214587387a |
0e848240448830537924465865611904 | s214587387a |
0e545993274517709034328855841020 | s878926199a |
例題:bugku-矛盾
打開網頁,看到一段 PHP 代碼:
$num=$_GET['num'];
if(!is_numeric($num))
{
echo $num;
if($num==1)
echo 'flag{**********}';
}
代碼首先用 get 方法讀取一個變量 num,注意到想要使 flag 顯示,需要運行代碼“echo 'flag{**********}';” 。此時需要 2 個 if 語句都成立,第一個 if 語句需要用 is_numeric() 判斷變量是否是數字,第二個 if 語句要用 “ == ”判斷變量是否等於 1。要使得一個變量值等於 1 且不是數字,這真是件矛盾的事情,不過通過傳遞一個弱類型的變量就解能出來。構造出 payload,提交獲得 flag。
?num=1abc
例題:攻防世界-simple_php
打開題目,看到一段 PHP 代碼。這段代碼需要用 get 方法接收 2 個變量,其中第一個變量 a 需要判斷是否為數字 0,而且直接判斷 a 要為真,第二個變量 b 需要不是一個數字,並且數值大於 1234。
<?php
show_source(__FILE__);
include("config.php");
$a=@$_GET['a'];
$b=@$_GET['b'];
if($a==0 and $a){
echo $flag1;
}
if(is_numeric($b)){
exit();
}
if($b>1234){
echo $flag2;
}
?>
很明顯又是一個弱類型,此時可以根據需求構造出 payload,提交獲得 flag。
?a=abc&&b=1235a
例題:bugku-十六進制與數字比較
題目的源碼如下,需要傳入一個字符串變量 password,返回 flag 的條件是 password 的值和 number 變量的值相等。但是在判斷兩個變量是否相等之前,代碼要先遍歷 password 字符串,如果字符串中的字符轉換為 ASCII 在 0 ~ 9 之間就會返回錯誤。
<?php
error_reporting(0);
function noother_says_correct($temp)
{
$flag = 'flag{test}';
$one = ord('1'); //ord — 返回字符的 ASCII 碼值
$nine = ord('9'); //ord — 返回字符的 ASCII 碼值
$number = '3735929054';
// Check all the input characters!
for ($i = 0; $i < strlen($number); $i++)
{
// Disallow all the digits!
$digit = ord($temp{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
// Aha, digit not allowed!
return "flase";
}
}
if($number == $temp)
return $flag;
}
$temp = $_GET['password'];
echo noother_says_correct($temp);
?>
注意到判斷變量使用的是 “==”,這又是熟悉的弱類型,只要 password 的數值和 number 一樣,可以使用其他進制來替換。因此傳入的 password 應該是 “3735929054” 其他進制的表示,結合題意應該是十六進制(注意十六進制以 0x 開頭):
?password = 0xpassword
例題:bugku-數組返回 NULL 繞過
題目的源碼如下,首先注意到第一個分支語句使用了 ereg() 函數,該函數用於搜索一個字符串中指定的字符串。分析一下 ereg() 函數中的第一個參數,方括號表示字符集,匹配大小寫字母和數字其中一個字符,^ 表示字符串開始,$ 表示字符串結束,+ 號表示重復 1 到多次,整個表達式表示匹配由多個數字大小字母組成的字符串。第二個分支中使用了 strpos() 函數,該函數用於查找字符串在另一字符串中第一次出現的位置(區分大小寫)。因此傳入的 password 應該滿足以數字或者字母開頭,且必須在 password 參數中找到 “--”。
<?php
$flag = "flag";
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
echo 'You password must be alphanumeric';
else if (strpos ($_GET['password'], '--') !== FALSE)
die('Flag: ' . $flag);
else
echo 'Invalid password';
}
?>
不過聰明的做法是傳個 “password[]” 數組進去,因為 ereg 和 strpos 函數都只能處理字符,傳入數組是返回的是 NULL。三個等號的時候不會觸發弱類型,所以 NULL 不等於 false 滿足條件。構造出 payload 提交:
?password[]=0
例題:bugku-urldecode 二次編碼繞過
題目的源碼如下,首先題目需要輸入變量 id,且變量 id 不能包含字符串 “hackerDJ”。接着使用 urldecode() 函數進行 url 解碼,需要令解碼后的字符串等於 “hackerDJ”。
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("not allowed!");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ"){
echo "Access granted!";
echo "flag";
}
?>
在 URL 編輯框中可以使用 URL-encode 來替代字符,例如 “h” 的 URL 編碼是 “%68”,此時輸入 “%68ackerDJ” 等同於輸入 “hackerDJ”。由於源碼會使用 urldecode() 函數進行 url 解碼,因此可以對 “%68ackerDJ” 進行 url 編碼,讓傳入的字符串通過第一個條件語句,在解碼之后通過第二個分支語句。所以提交的 payload 應該是 “%68ackerDJ” 的 URL 編碼。
?id = %2568ackerDJ
例題:bugku-md5()函數
題目的源碼如下,首先注意到第一個分支語句,如果 username 和 password 2 個變量相等會導致無法獲得 flag。但是第二個分支條件又要求 username 變量和 password 變量經過 md5() 函數加密的結果相同。
<?php
error_reporting(0);
$flag = 'flag{test}';
if (isset($_GET['username']) and isset($_GET['password'])) {
if ($_GET['username'] == $_GET['password'])
print 'Your password can not be your username.';
else if (md5($_GET['username']) === md5($_GET['password']))
die('Flag: '.$flag);
else
print 'Invalid password';
}
?>
注意此時遇到的是 “===” ,不過也不是代表無從下手。在 md5() 函數傳入數組時會報錯返回 NULL,當 2 個變量都導致報錯返回 NULL 時就能使使得條件成立。構造出 payload 提交:
?username[]=1&password[]=0
例題:bugku- md5 加密相等繞過
題目的源碼如下,我們需要傳入一個變量 “a”,這個變量 a 的要求是經過 md5 加密之后和 “QNKCDZO” 的加密結果相同。
<?php
$md51 = md5('QNKCDZO');
$a = @$_GET['a'];
$md52 = @md5($a);
if(isset($a)){
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "flag{*}";
}
else {
echo "false!!!";
}
}
else{echo "please input a";}
?>
“QNKCDZO” md5 加密結果為 “0E830400451993494058024219903391”,注意這是個 “0e” 開頭的值,利用 “==” 判斷相等時會被視為 0。因此現在需要傳入的是另一個字符串,該字符串的 md5 加密后的字符串也是 0e 開頭的即可。