题目引入

来看一个 CTF Web 题目,这道题目的 PHP 源代码已经给出来了,先来审计一下。

(题目来源:2018 SYSUCSA 招新赛)

<?php
$a = @$_GET['a'];
$b = @$_GET['b'];
$a_md5 = @md5($a);
$b_md5 = @md5($b);
if(isset($a) && isset($b)){
    if ($a != $b && $a_md5 == $b_md5) {
        echo "flag{xxxxxxx}";
    } else {
        echo "Sorry, just think more";
    }
}  
else{
    echo "I guess you know how to see the source code";
}
?>

从第 7 行可以看出,该题要求输入两个不相等的变量 $a$b,而其 MD5 值却“相等”。

本文地址:https://www.jeddd.com/article/php-hash-comparison-flaw.html

弱类型比较

PHP 中存在 ==`!====`!== 两组运算符。PHP 手册中有如下一段话:

如果用 ==!= 比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。当用 ===!== 进行比较时则不进行类型转换,因为此时类型和数值都要比对。

请看以下例子:

$a1 = '123';
$a2 = 123;
echo ($a1 == $a2) ? 'Yes' : 'No';  // Yes
echo ($a1 === $a2) ? 'Yes' : 'No'; // No

结果分别为“Yes”和“No”,可以看出,整数 123 和字符串'123'是相等的,但是不全等。

如果字符串中含有科学记数法(e 表示法)呢?请继续阅读以下代码:

$b1 = '3e1';
$b2 = 30;
echo ($b1==$b2) ? 'Yes' : 'No';  // Yes
$c1 = '3e5';
$c2 = '300000';
echo ($c1==$c2) ? 'Yes' : 'No';  // Yes

结果分别为“Yes”和“Yes”。请注意,'3e1'代表 $ 3\times 10^1 $,它与整数 30 相等'3e5'代表 $ 3\times 10^5 $,它与字符串'300000'也相等!到此,我们大致理解了 PHP 弱类型比较的特点了。

接下来,再阅读一段代码,感受一下弱类型比较与 Hash 比较缺陷之间的关系。

$d1 = '0e12345';
$d2 = '0e67890';
echo ($b1==$b2) ? 'Yes' : 'No';  // Yes

是的,这段代码同样会输出“Yes”。这是因为 $d1 和 $d2 两个字符串均以 '0e' 开头,用科学记数法表示就是 $ 0\times 10^n $,而 0 乘以任何数都为 0,因此它们相等就不足为怪了。

Hash 比较缺陷

如果采用 == 运算符比较哈希值,同样会面临上面讲的弱类型比较的问题,即任何以'0e'开头的字符串都相等

所以解决本文开头 CTF 题目的关键点就在于,找到两个不同的字符串,其 MD5 值都以'0e'开头。以下表格列出了一些 MD5 值是'0e'开头的字符串:

源字符串MD5(32位)
s878926199a0e545993274517709034328855841020
s155964671a0e342768416822451524974117254469
s214587387a0e848240448830537924465865611904
s214587387a0e848240448830537924465865611904
s878926199a0e545993274517709034328855841020
s1091221200a0e940624217856561557816327384675
s1885207154a0e509367213418206700842008763514
s1502113478a0e861580163291561247404381396064
s1885207154a0e509367213418206700842008763514
s1836677006a0e481036490867661113260034900752
s155964671a0e342768416822451524974117254469
s1184209335a0e072485820392773389523109082030
s1665632922a0e731198061491163073197128363787
s1502113478a0e861580163291561247404381396064
s1836677006a0e481036490867661113260034900752
s1091221200a0e940624217856561557816327384675
s155964671a0e342768416822451524974117254469
s1502113478a0e861580163291561247404381396064
s155964671a0e342768416822451524974117254469
s1665632922a0e731198061491163073197128363787
s155964671a0e342768416822451524974117254469
s1091221200a0e940624217856561557816327384675
s1836677006a0e481036490867661113260034900752
s1885207154a0e509367213418206700842008763514
s532378020a0e220463095855511507588041205815
s878926199a0e545993274517709034328855841020
s1091221200a0e940624217856561557816327384675
s214587387a0e848240448830537924465865611904
s1502113478a0e861580163291561247404381396064
s1091221200a0e940624217856561557816327384675
s1665632922a0e731198061491163073197128363787
s1885207154a0e509367213418206700842008763514
s1836677006a0e481036490867661113260034900752
s1665632922a0e731198061491163073197128363787
s878926199a0e545993274517709034328855841020

写在后面

要在 PHP 中比较 Hash 值,应该考虑使用hash_equals函数(PHP 5.6 及以上),关于该函数的更多信息请参考 PHP 手册:http://php.net/hash_equals

References

  1. PHP: 比较运算符 - Manual

本文地址:https://www.jeddd.com/article/php-hash-comparison-flaw.html