MD5的弱类型比较和强碰撞and md5()函数的原始二进制数据探究

MD5的弱类型比较和强碰撞and md5()函数的原始二进制数据探究

二月 16, 2021
前言

关于PHP中== 和===的用法:

  • ===是恒等计算符,同时检查表达式的值与类型; ==是比较运算符,不会检查条件表达式的类型。
  • PHP使用==比较数字和字符串时,将字符串转换为数字后与数字进行比较:
1
2
字符串以数字开头:取前面的数字;
字符串不以数字开头:0
  • PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0.

    所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。

==比较
解法一

由于md5不能加密数组,在加密数组是会返回NULL,因此,我们可以传入两个数组进行md5缺陷绕过。

解法二

可以传入两个md5加密后是0e开头的字符串,这个0e开头的字符串,后面只能为纯数字,这样PHP在进行科学计算法时才会将它转化为0.

  • 脚本
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";
}
}
?>
  • 一些值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675
===比较

强类型比较,不仅比较值,还比较类型,0e会被当做字符串,所以不能用0e来进行。

解法一

也可以通过md5缺陷绕过传入两个数组来做,但不能在使用0e开头的字符串,因为===是md5的强碰撞,进行了严格的过滤。

解法二

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

我们可以使用快速MD5碰撞生成器来构建两个MD5一样,但是内容完全不一样的字符。

程序:http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip

源码:http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5_source.zip

构造

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

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

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

1

测试

对生产的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>
1
2
3
4
5
6
7
8
9
10

二进制md5加密 8e4ef6c69a337c0de0208455ee69a416

url编码 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%C4P%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%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编码后的两个字符串不完全相同,满足我们输入两个不同参数的需要。

2

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

原文传送门: https://blog.csdn.net/qq_38154820/article/details/113750318

例题

BUUCTF__[BJDCTF2020]Easy MD5

  • 一开始进入看到查询框就还以为是SQL注入,输入万能密码、1、单引号啥的都没啥反应,明明在看到是get传参password嘛,还是没啥头绪,就翻一下wp,emmm 是藏在消息头里的SQL语句。

3

解题的关键还在于SQL语句password=md5($pass,true)的理解。

如下:

md5(string, raw) raw 可选,默认为false

true:返回16字符2进制格式

false:返回32字符16进制格式

简单来说就是 true将16进制的md5转化为字符了,如果某一字符串的md5恰好能够产生如’or ’之类的注入语句,就可以进行注入了。

提供一个字符串:ffifdyop

md5加密后的值为:276f722736c95d99e921722cf9ed621c

转成字符串后: ‘or’6 (关于这一步骤的具体内容会在本文章的最后作为补充。)

就是说password=md5($pass,true)返回的是字符串,而输入ffifdyop查询之后就会执行SQL语句:select * from ‘admin’ where password=’ ‘or ‘6’

  • 之后返回如下页面,查看源码,知道是一个弱类型的比较,get方式构造payload绕过:**?a[]=1&b[]=2**

​ md5不能处理数组,遇到数组不报错但是会将其转变成NULL,null=null。所以采用md5缺陷绕过。

4

​ 当然也可以采用上述的解法二运用0e开头的md5值相同的字符串进行传参。

如:

1
2
3
?a=QNKCDZO&b=240610708
?a=QNKCDZO&b=s155964671a
?a=s878926199a&b=s155964671a
  • 得到一个新的页面,代码审计,POST方式,传param1和param2两个参数,这两个参数还不能相等,但是md5转换后的值还要相等(0e开头),md5碰撞的强类型比较,依然可以采用md5数组绕过,当然解法二中运用字符串也是可以的。

构造payload如下:param1[]=1&param2[]=2

5


一些关于快速md5碰撞生成器的补充

使用fastcoll进行md5碰撞:

  1. 生成两个文件:
1
fastcoll_v1.0.0.5.exe -p C:\windows\notepad.exe -o D:\notepad1.exe D:\notepad2.exe
  1. 6
  • 在winhex中对比发现文件内容不一样:

7

8

  • 使用 certutil 命令查看一下md5;

9

  1. 发现文件虽然内容不一样,但是md5值是一样的,说明了用 md5 做文件校验并不安全。

两个程序文件的MD5一致,却又都能正常运行,并且可以做完全不同的事情:

1
2
http://www.win.tue.nl/hashclash/SoftIntCodeSign/HelloWorld-colliding.exe
http://www.win.tue.nl/hashclash/SoftIntCodeSign/GoodbyeWorld-colliding.exe

这两个程序会在屏幕上打印出不同的字符,但是它们的MD5都是一致的。

10

使用领域

在安全领域,已经推荐sha256和sha512了,sha1已经倾向于不推荐,md5可以认为已经被破解。

目前MD5被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等;
目前SHA1的应用较为广泛,主要应用于CA和数字证书中,另外在目前互联网中流行的BT软件中,也是使用SHA1来进行文件校验的。

  • MD5、SHA1虽然被发现存在缺陷(碰撞),但在近几年内,仍然可以大量使用
  • SHA256/384/512 的速度较慢,可以用于少量数据摘要,目前不适合用于大文件校验

写在最后
PHP中md5()函数如何对字符串进行加密

php的md5()具有输出原始二进制数据的特性。

md5()语法:

1
2
3
md5(string, raw) raw 可选,默认为false
true:返回16字符2进制格式
false:返回32字符16进制格式

通过使用php的md5(“ffifdyop”,true)输出其加密后的原始二进制数据得到目标字符串.

原始二进制数据不是指100111这些二进制数据,而是原始字符串转换成ascii码后组成的字符串。

  1. 使用md5(“ffifdyop”)进行加密
    通过加密后将会得到之后的32位16进制字符串:276f722736c95d99e921722cf9ed621c

  2. 将32位16进制字符串按照2个字符为一组切割成为16组16进制的字符串
    切割成27,6f,72,27,36,c9,5d,99,e9,21,72,2c,f9,ed,62,1c

  3. 将每一组16进制的数值转换成2进制 100111,1101111,1110010,100111,110110,11001001,1011101,10011001,11101001,100001,1110010,101100,11111001,11101101,1100010,11100

  4. 将每一组2进制数值转换称为10进制数值
    39,111,114,39,54,201,93,153,233,33,114,44,249,237,98,28

  5. 最后对照如下的ASCII码表即可翻译的出最终的原始二进制字符串.

11

以前四组为例39=>’,111=>o,114=>r,39=>’,最终的组成’or’这一个字符串.

md5()函数主体原文:

https://www.7gugu.com/2020/01/15/%E6%8E%A2%E7%A9%B6php%E7%9A%84md5%E5%87%BD%E6%95%B0%E8%BE%93%E5%87%BA%E7%9A%84%E5%8E%9F%E5%A7%8B%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E6%8D%AE%E6%98%AF%E5%95%A5/