php回顾之md5

前言

我们在ctf赛题中不乏看到这样的判定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$a = $_POST['a'];
$b = $_POST['b'];
if($a != $b){
if(md5($a) == md5($b)){
echo '弱类型比较ok ';}
else{
echo '弱类型比较no ';}

if(md5($a) === md5($b)){
echo '强类型比较ok ';}
else{
echo '强类型比较no ';}
}
else {
echo '不能输入相同的值 ';
}
highlight_file(__FILE__);
?>

我们可以看到这是一个典型的md5碰撞题目,题目中判定了a和b的md5值是否相等,所以我们可以构造a和b的md5值相等,但是a和b不相等的情况,从而绕过判定。sha1也存在这样的问题,这里会稍微牵扯一下。

md5简介

MD5(Message-Digest Algorithm 5)是一种广泛使用的哈希函数,它将任意长度的消息压缩成一个128位的哈希值。
MD5算法由Ronald Rivest在1991年设计,旨在提供一种快速且高效的消息摘要生成方法。MD5主要用于验证数据完整性和数字签名,但由于其安全性问题,现已被认为不适合用于密码学应用。
我院王小云院士之前的研究就与md5碰撞相关……

可行的手段

弱比较

0e绕过

在php中,使用==进行比较时,会进行类型转换,这就导致了一些意想不到的结果。
我们知道,md5格式为32位16进制数,必然存在一些特殊的md5长这个样子:0e830400451993494058024219903391
0e开头,在弱比较中,0e表示科学计数法中的0乘以10的某次方,因此无论后面的数字是什么,都会被认为是0。
所以,当我们构造a和b的md5值相等,但是a和b不相等的情况时,a和b的md5值都会被认为是0,从而绕过判定。

sha1的0e绕过
1
2
aaroZmOk
aaK1STfY

自身绕过

存在这样一个判断

1
$a==md5($a)

这就要求我们找到一个0e开头的$a,MD5后也是0e开头

$a=0e215962017

双重md5下的0e绕过

存在这样一个判断

1
$a==md5(md5($a))

比如这些

1
2
3
7r4lGXCH2Ksu2JNT3BYM
CbDLytmyGm2xQyaLNhWn
770hQgrBOjrcqftrlaZk

即可绕过

强比较

一般来说强比较能行的也能过弱比较()
因此我们就直接来谈它吧

数组绕过

MD5一个数组返回了null,这样null===null就成立了

1
2
3
4
5
6
<?php
$a = array();
$b = array();
if($a === $b){
echo 'ok';
}

就进去了()

内置类绕过

我们知道当一个对象被转换为字符串时,会调用__toString()方法,而当一个对象被转换为整数时,会调用__intval()方法。
我们可以利用这两个方法,来绕过强比较。
比如这样

1
2
3
4
5
6
7
8
9
<?php
$a=new Exception('12','123');$b=new Exception('12','321');
if(md5($a)===md5($b)){
echo 'ok';
}
else{
echo 'not ok';
}

这里面有很多需要注意,否则就会像我一样开始进不去()

  1. Exception的第一个参数是message,第二个参数是code,code必须是整数,否则会报错
  2. Exception第一个参数message必须一样,否则会报错
  3. 最关键的是,必须在一行,不能换行,否则会报错,这个时候可能牵扯到适用性问题,注意即可,小trick
应用

当然,比起它可以绕过的作用,我认为可以关注一种具体情形
详细可见
[极客大挑战 2020]Greatphp
即在反序列的情景下,既可以满足不可换行,绕过md5验证,又可以控制message的值,从而实现RCE,第一次见还是很惊讶的

限制

目前有两种内置类可供使用

1
2
Error(只在php7)
Exception(php5、7都可)

天选payload

有可打印字符鉴定,就无法使用fastcoll工具(后续会讲到)
如果有字符串鉴定,就无法使用数组绕过
有换行,也不能内置类绕过
没招了()
可以使用这个payload

1
2
TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

如果看到这个,你之前不知道,那你绝对没白来()
两个字符串中间只有一个字符的差异,为何会MD5相等,它们是 一对 MD5 碰撞样本,等待大佬解释

拓展

md5碰撞生成:fastcoll工具

fastcoll是一个用于生成MD5碰撞的工具。它利用了MD5算法的弱点,能够在短时间内找到两个不同的输入数据,这些数据在经过MD5哈希函数处理后会产生相同的哈希值。

使用场景

如果一道题目会对你上传的两张图片进行md5验证,而你上传的两张图片的md5值相等,但是它们的内容不相等,这时候就可以使用fastcoll工具生成碰撞样本,从而绕过md5验证。
fastcoll工具下载

使用方法

详细见工具说明
一般我们根据一个文件生成两个md5值一样的不同文件,这个时候可以

1
2
3
4
5
6
7
<?php
$a=file_get_contents('1.txt');
$b=file_get_contents('2.txt');
echo urlencode($a)."<br>";
echo urlencode($b)."<br>";
echo md5($a)."<br>";
echo md5($b)."<br>";

即可得到urlencode后的字符串,直接提交即可

sql里的MD5

如果大家了解到sql的万能密码就可以继续看下去了
使用 md5($val, true) 计算的 md5 转换为字符串时会被以16进制数据解析为字符串
即,如果实现了md5($val, true),那么只要我们传入一个字符,它的md5值被解析为16进制数时,正好可以实现这种效果

1
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '' or true";

那么就可以万能密码登录
比如如下字符

1
2
3
ffifdyop

129581926211651571912466741651878684928

哈希长度扩展攻击(加盐)

介绍

哈希长度扩展攻击(Hash Length Extension Attack)是一种针对某些哈希函数的攻击方法,允许攻击者在不知道原始消息内容的情况下,计算出一个新的消息及其对应的哈希值。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
include "flag.php";
$secretKey = 'xxxxxx'; #xxx为未知内容,但长度已知为6。
$v1 = $_GET['str'];
$sign = $_GET['sign'];
$token = md5($secretKey.$v1);#$secretKey只知道位数而不知道具体字符
if($v1 === 'test') {
die($token); #token=2df51a84abc64a28740d6d2ae8cd7b16
} else {
if($token === $sign) {
die($flag);
}
}
?>

基于此,可以进行哈希拓展攻击
具体需要了解md5的实现,当然网上有很多文章,我这里简述一下
MD5会把字符转化为512位的二进制块
填充的逻辑在这

1
2
明文数据的二进制数据长度<=448,填充padding(无意义占位)数据使其长度为448,再添加原始明文数据的二进制长度信息(64位)使其长度为512位即可。
448<明文数据的二进制数据长度<=512,填充padding数据至下一块的448位,而后再添加原始明文数据的二进制长度信息(64位)使其长度为512位即可。

这样的话,我们在$v1这一部分操作一下

md5($secret.$v1)
v1="test" + padding数据 + 长度数据 + "abc"
逻辑是,知道位数之后就可以知道padding数据和长度数据,这样的话可以生成

$secret."test".padding数据 + 长度数据—>md5
这样的话可以从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
而最后的MD5值就是这最后的向量串经过如下转换的结果。

如向量串:

A=0xab45bc01
B=0x6a64bb53
C=0x23ba8afe
D=0x46847a62

先两两为一组进行组合,得到如下数据:

ab 45 bc 01
6a 64 bb 53
23 ba 8a fe
46 84 7a 62

再进行高低位互换,得到如下数据:

01 bc 45 ab
53 bb 64 6a
fe 8a ba 23
62 7a 84 46

最终拼接得到MD5值:01bc45ab53bb646afe8aba23627a8446。

我们根据服务回显出的md5反推出最后一个向量串
再回到
v1="test" + padding数据 + 长度数据 + "abc"
这里前部分是完整的若干512块,会生成一个向量串
然后倒数第二个向量串和abc那块512位进行运算,得到最后的md5

工具

hashdump
详细见文档

后记

ctf里面有关md5的考点(我见过)大抵如此,如果有其他考点,欢迎补充