题目引入
来看一个 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位) |
---|---|
s878926199a | 0e545993274517709034328855841020 |
s155964671a | 0e342768416822451524974117254469 |
s214587387a | 0e848240448830537924465865611904 |
s214587387a | 0e848240448830537924465865611904 |
s878926199a | 0e545993274517709034328855841020 |
s1091221200a | 0e940624217856561557816327384675 |
s1885207154a | 0e509367213418206700842008763514 |
s1502113478a | 0e861580163291561247404381396064 |
s1885207154a | 0e509367213418206700842008763514 |
s1836677006a | 0e481036490867661113260034900752 |
s155964671a | 0e342768416822451524974117254469 |
s1184209335a | 0e072485820392773389523109082030 |
s1665632922a | 0e731198061491163073197128363787 |
s1502113478a | 0e861580163291561247404381396064 |
s1836677006a | 0e481036490867661113260034900752 |
s1091221200a | 0e940624217856561557816327384675 |
s155964671a | 0e342768416822451524974117254469 |
s1502113478a | 0e861580163291561247404381396064 |
s155964671a | 0e342768416822451524974117254469 |
s1665632922a | 0e731198061491163073197128363787 |
s155964671a | 0e342768416822451524974117254469 |
s1091221200a | 0e940624217856561557816327384675 |
s1836677006a | 0e481036490867661113260034900752 |
s1885207154a | 0e509367213418206700842008763514 |
s532378020a | 0e220463095855511507588041205815 |
s878926199a | 0e545993274517709034328855841020 |
s1091221200a | 0e940624217856561557816327384675 |
s214587387a | 0e848240448830537924465865611904 |
s1502113478a | 0e861580163291561247404381396064 |
s1091221200a | 0e940624217856561557816327384675 |
s1665632922a | 0e731198061491163073197128363787 |
s1885207154a | 0e509367213418206700842008763514 |
s1836677006a | 0e481036490867661113260034900752 |
s1665632922a | 0e731198061491163073197128363787 |
s878926199a | 0e545993274517709034328855841020 |
写在后面
要在 PHP 中比较 Hash 值,应该考虑使用hash_equals
函数(PHP 5.6 及以上),关于该函数的更多信息请参考 PHP 手册:http://php.net/hash_equals。
References
本文地址:https://www.jeddd.com/article/php-hash-comparison-flaw.html
那用===比较哈希可以么
可以