0x01 前言
之前在俱乐部CTF平台上做了一道md5弱比较的题,在网上找到了题解,就是根据强弱类型比较的特性来对其进行绕过,我只是看到了加密结果以0E开头的就可以绕过弱类型比较,这个是利用了PHP处理Hash的缺陷,而hash加密结果以0e开头的payload就可以利用这个缺陷来进行绕过。这篇文章就是我对这个缺陷的了解,同时,也涉及到了强类型比较和“Md5强碰撞”。
0x02 正文
科学计数法
首先要提到一点的是计算机中的科学计数法,它是一种带有 E
的表示方法。比如, 1234
用科学计数法表示为1.234e3
,表示的是1.234*10^3
;123456.7
的表示为 1.234567e5
, 表示的是1.234567*10^5
;由此可知,以 0e
开头的科学技术法表示的值都为 0。
哈希加密结果比较
在哈希加密结果当中,会出现以 0e
开头 + 纯数字
的组合,这种 特殊的Hash
也被称作 Magic Hash
,这种情况会被当作科学计数法(被当作数字类型)来识别。因为这个缺陷,导致了在比较哈希值的时候,有了绕过的方式。这里提一下,科学计数法当中存在 INF
,表示极大的意思,如:
<?php
var_dump(10e982137421342135);
# 返回值 float(INF)
弱类型比较
在弱类型比较,符号为 ==
和 !=
,这种比较方式只会比较值,不会比较数据类型,它是将两个变量转换为同一种类型然后进行比较
<?php
var_dump(0 == '0'); # ①bool(true)
var_dump(0e01 == 0); # ②bool(true)
var_dump('0e01' == 0e01); # ③bool(true)
var_dump('0e0a' == 0); # ④bool(false)
这个例子当中就可以得知:
通过 ①、②、③ 的推断出,
0
,0e01
,'0e01'
的值是相等的,达到这一条件只能是他们的值都是数字类型
(科学计数法)。在 ④ 中,因为
'0e0a'
无法转为数字类型
的数据,所以它只能将后者转为字符串格式,导致值不相等。
强类型比较
而在强类型比较中,比较的不仅值,还有数据类型,这就意味着它不会将比较类型进行转换,只要不是同一类型就会返回 false
<?php
var_dump(0 === '0'); # ①bool(false)
var_dump('0e01' === '0'); # ②bool(false)
var_dump(0e01 === 0); # ③bool(false)
var_dump(0e01 === 0.0); # ④bool(true)
强类型比较相较于弱类型理解起来就简单多了(没那么复杂),这里不同类型的比较就不多说了,在类型相同的前提下,我们的比较当中:
在 ② 当中,类型不会被转换,直接对比值
在 ③ 和 ④ 里面,
0e01
的类型为float
,0
为int
,0.0
为float
,三者的值都是相同的,但类型只有两者是相同。
题型
所以在遇到哈希值强弱类型比较的时候,就可以利用这个哈希缺陷。以这两个字符串和字符串的哈希值为例:
弱类型比较
在弱比较的情况下,是这样子的
<?php
$a = $_GET['a'];
if (isset($a)){
if ($a == md5($a)) {
echo 'OK';
} else {
echo 'Failed';
}
} else {
echo 'You need to post the parameter a and b';
}
# 传入 a=0e215962017,返回的结果是 OK
强类型比较
而在强比较的时候,有了不同
<?php
$a = $_GET['a'];
$b = $_GET['b'];
if (isset($a) && isset($b)){
if (md5($a) === ma5($b) && $a !== $b){
echo 'OK';
} else {
echo 'Failed';
}
} else {
echo 'You need to post the parameter a and b';
}
# 传入 a=QLTHNDT&b=QNKCDZO,返回的结果是 Failed
显而易见,这里失败就是因为MD5加密结果的值不同,这里就可以引入一个新的方法,利用的是 哈希函数
在处理非字符数据的时候返回异常,如这里传入的是数组,那么返回的就会是 NULL
,这里就可以利用 NULL
来对比较进行绕过
# 传入 a[]=1&b[]=2,返回的结果是 OK
MD5强碰撞
当对传参进行控制的时候,强比较就不能再像上面那样子使用数组传入一个参数就解决问题
<?php
$a = (string)$_GET['a'];
$b = (string)$_GET['b'];
if (isset($a) && isset($b)) {
if (md5($a) === md5($b) && $a !== $b) {
echo 'OK';
} else {
echo 'Failed';
}
} else {
echo 'You need to pass the parameter a and b';
}
# # 传入 a[]=1&b[]=2,返回的结果是 Failed
这就引出了一个问题,是否存在md5加密前不相同,加密后相同的字符串?
在网上搜索资料后,发现是存在经md5加密后,加密值相同,原本不同的字符串(表示为“md5冲突”)。
这里给出payload
<?php
$s1 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab";
$s2 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab";
$s3 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%ed%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%a7%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%e6%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%16%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%33%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%6f%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab";
var_dump(md5(urldecode($s1)));
var_dump(md5(urldecode($s2)));
var_dump(md5(urldecode($s3)));
# 运行结果
# string(32) "ea8b4156874b91a4ef00c5ca3e4a4a34"
# string(32) "ea8b4156874b91a4ef00c5ca3e4a4a34"
# string(32) "ea8b4156874b91a4ef00c5ca3e4a4a34"
从这里我们得到了md5加密结果相同但不一样的字符串,深究这个原理参考了网上的这篇文章 https://crypto.stackexchange.com/questions/1434/are-there-two-known-strings-which-have-the-same-md5-hash-value
补充:但除了这种方法,还有一种绕过的方式,就是以数组的方式来传递,使得其获取值类型为数组,但数组在MD5加密后,返回的值是NULL
,这就可以绕过md5($a) === md5($b) && $a !== $b
。
<?php
error_reporting(0);
$a = $_GET['a'];
$b = $_GET['b'];
var_dump(md5($a));
echo "<br>";
var_dump(md5($b));
// a[]=1&b[]=2,输出两个NULL
0x03 总结
在哈希比较当中,弱类型比较可以利用“科学计数法”来进行绕过;强类型比较在不做传入参数类型限制的情况下,可以利用到哈希函数处理非字符数据时返回异常,使得结果为 NULL,而在做了限制之后,就需要利用“Md5冲突”了。这里的弱类型比较没有限制是因为限制一般都是将传入的参数转为字符串类型,而弱类型比较所利用到的缺陷就是字符串加密结果为“科学计数法”形式的字符串,所以无法限制弱类型比较。
参考文章:
PHP哈希弱类型比较的缺陷_php弱类型hash比较缺陷_ADreamClusive的博客-CSDN博客
php中hash比较缺陷_php代码审计-php-hash比较缺陷_swag_000的博客-CSDN博客
参与讨论