CTF中关于md5的一些总结

CTF中关于md5的一些总结

前言

最近打了挺多ctf,碰到挺多关于md5的一些问题,或者一些变种的题目,虽然已经是烂大街的问题了,但是还是需要总结一下,方便下次比赛可以直接用脚本

CTF中的一些案例

案例1——ciscn2020初赛——easytrick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class trick{
public $trick1;
public $trick2;
public function __destruct(){
$this->trick1 = (string)$this->trick1;
if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
die("你太长了");
}
if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
echo file_get_contents("/flag");
}
}
}
highlight_file(__FILE__);
unserialize($_GET['trick']);

这里我们抛开反序列化不谈,这题本质上需要我们构造trick1=NANtrick2=NAN即可绕过。原理也很简单,NaN与所有值都不相等,包括它自己。当然也可以用INF绕过,原理类似,这里不过多赘述

案例2——强网杯2020——Funhash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
die('level 1 failed');
}

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
die('level 2 failed');
}

//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
var_dump($row);
$result->free();
$mysqli->close();

这里重点关注level 1,其他两关都是老生常谈的东西,无须过多赘述。这里我已经写好了脚本,下次遇到这种md4的题目直接爆破干它

下面给出我已经爆破好的例子:

1
2
0e251288019
0e898201062

exp.php

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
<?php
$payload = "0123456789";
function calc_md4($s){
$s = "0e".$s;
$md4 = hash("md4", $s);
if (substr($md4, 0, 2) === "0e" && ctype_digit(substr($md4, 2))) {
echo $s.PHP_EOL;
}
}

function getstr($payload,$s,$slen){
if(strlen($s) == $slen){
calc_md4($s);
return $s;
}
for($i = 0;$i<strlen($payload);$i++){

$sl = $s.$payload[$i];
getstr($payload, $sl, $slen);
}

}

//字符串长度从3到30,肯定找得到
for($i = 3;$i<30;$i++){
getstr($payload,'',$i);
}

案例3——NJUPT2019南邮校赛——easyphp

这里我截取了一部分代码,用作案例讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$string_1 = $_GET['str1'];
$string_2 = $_GET['str2'];
//2nd
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 '2nd ok'."<br>";
}
else{
die("can u give me the right str???");
}
}
else{
die("no!!!!!!!!");
}
}

这里的关键就是,需要我们找两个个是ce开头的,然后ce后面是纯数字的md5值

下面给出我已经爆破好的例子:

1
2
N9KU3
QlYRH

爆破的脚本如下

exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import string
import hashlib
payload = string.ascii_letters+string.digits
def calc_md5(s):
md5 = hashlib.md5(s.encode("utf-8")).hexdigest()
if (md5[0:2] == "ce" and md5[2:].isdigit()) :
print(s)

def getstr(payload,s,slen):
if(len(s) == slen):
calc_md5(s)
return s

for i in payload:
sl = s+i
getstr(payload, sl, slen)

# 字符串长度从0到30,肯定找得到
for i in range(3,30):
getstr(payload,'',i)

案例4——双MD5碰撞绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

if (isset($_GET['a']) && isset($_GET['b'])) {
$a = $_GET['a'];
$b = $_GET['b'];
if ($a != $b && md5($a) == md5(md5($b))) {
echo "flag{XXXXX}";
} else {
echo "wrong!";
}

} else {
echo 'wrong!';
}
?>

这里其实我们只要找出md5(md5($b))0e开头的且0e后面是纯数字的字符串即可

下面给出我已经爆破好的例子:

1
2
f2WfQ
iv2Cn

爆破脚本如下:

exp.py

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
import string
import hashlib

payload = string.ascii_letters + string.digits


def calc_md5(s):
md5 = hashlib.md5(s.encode("utf-8")).hexdigest()
md5_double = hashlib.md5(md5.encode("utf-8")).hexdigest()
if (md5_double[0:2] == "0e" and md5_double[2:].isdigit()):
print(s)


def getstr(payload, s, slen):
if (len(s) == slen):
calc_md5(s)
return s

for i in payload:
sl = s + i
getstr(payload, sl, slen)


# 字符串长度从0到30,肯定找得到
for i in range(3, 30):
getstr(payload, '', i)

案例5——php中md5($str,true)注入

1
2
3
4
5
6
7
8
9
10
<?php
$password = $_POST['password'];
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";
$result = mysqli_query($link,$sql);
if(mysqli_num_rows($result)>0){
echo 'Success';
}else{
echo 'Failure';
}
?>

这里md5输出的是16 字符二进制格式,并不是我们平时看到的32字符十六进制数,所以我们只需要找md5加密后字符串中是否存在'or'字符串

下面给出我已经爆破好的例子:

1
2
3
4
5
ffifdyop
4SV7p
bJm4aG
bNas5p
ckHAEb

exp.php

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
<?php
$payload = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
function calc_md5_true($s)
{
$md5_true = md5($s,true);
if (strpos($md5_true,"'or'") !== false){
echo $s.PHP_EOL;
}
}

function getstr($payload, $s, $slen)
{
if (strlen($s) == $slen) {
calc_md5_true($s);
return $s;
}
for ($i = 0; $i < strlen($payload); $i++) {

$sl = $s . $payload[$i];
getstr($payload, $sl, $slen);
}

}

//字符串长度从3到30,肯定找得到
for ($i = 3; $i < 30; $i++) {
getstr($payload, '', $i);
}

案例6——强网杯2018——Web 签到

这里选取了部分代码,用作演示

1
2
3
4
<?php
if((string)$_POST['param1']!==(string)$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])){
die("success!");
}

这里因为对两个参数都进行了强制类型转换,所以一般的方法(用数组报错绕过肯定是行不通的了),所以我们必须找到两个文件,他们的内容不一样,但是md5值相等

在网上找到两张图片,他们内容不一样,但是md5值一样

double_md5_1

double_md5_2

exp.py

1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
import requests as req
url = 'http://localhost:82/WWW/test2.php'
with open('double_md5_1.jpg','rb') as f:
md51 = f.read()
with open('double_md5_2.jpg','rb') as f:
md52 = f.read()

data = {'param1':md51,'param2':md52}
r = req.post(url=url,data=data)
print(r.text)

案例7——md5($a)与md5(md5($a))都是0e开头的字符串

下面给出我已经爆破好的例子:

1
2


exp.py

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
import string
import hashlib

payload = string.ascii_letters + string.digits


def calc_md5(s):
md5 = hashlib.md5(s.encode("utf-8")).hexdigest()
md5_double = hashlib.md5(md5.encode("utf-8")).hexdigest()
if (md5[0:2] == "0e" and md5[2:].isdigit() and md5_double[0:2] == "0e" and md5_double[2:].isdigit()):
print(s)


def getstr(payload, s, slen):
if (len(s) == slen):
calc_md5(s)
return s

for i in payload:
sl = s + i
getstr(payload, sl, slen)


# 字符串长度从0到30,肯定找得到
for i in range(3, 30):
getstr(payload, '', i)

拓展

三张图片碰撞

在上个案例中,只是两张图片碰撞。这里我们思考一个问题,如果是三张图片呢,是否存在?答案是存在的

three_md5_1

three_md5_2

three_md5_3

两个其他类型的文件md5碰撞

首先下载hashclash,如果是linux环境的话可以下载编译好的二进制文件,如果是macos系统的话,就需要下载源码,然后自行编译,编译成功如下:

image-20200828160628187

创建目录

1
2
3
mkdir ipc_workdir

cd ipc_workdir

运行脚本

1
2
3
echo -n "TEST" > prefix.txt

../scripts/poc_no.sh prefix.txt

稍等片刻后,就可以碰撞出来了

image-20200828164834105

php文件的md5碰撞

下载文件:

1
2
3
wget https://s3-eu-west-1.amazonaws.com/md5collisions/a.php

wget https://s3-eu-west-1.amazonaws.com/md5collisions/b.php

image-20200828205602032

How I made two PHP files with the same MD5 hash

sha1碰撞

下载文件

1
2
3
curl -sSO https://shattered.it/static/shattered-1.pdf

curl -sSO https://shattered.it/static/shattered-2.pdf

image-20200828173329579

任意多个文件,文件内容不同,md5值相同

运用的原理如下:

1
MD5(file + col1_a + col2_a) === MD5(file + col1_a + col2_b) === MD5(file + col1_b + col2_a) === MD5(file + col1_b + col2_b)

这里使用的工具还是hashclash

我们在在hashclash文件夹下创建工作目录,然后执行下面的shell脚本

gen.sh

1
2
3
4
../bin/md5_fastcoll test.txt -o test_1.txt test_2.txt
../bin/md5_fastcoll test_1.txt -o test_1_1.txt test_1_2.txt
../bin/md5_fastcoll test_1_1.txt -o test_1_1_1.txt test_1_1_2.txt
./genfiles.py

genfiles.py

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
with open("test.txt","rb") as f:
base = f.read()

with open("test_1.txt","rb") as f:
file_1 = f.read()
coll_1 = file_1[len(base):]

with open("test_2.txt","rb") as f:
file_2 = f.read()
coll_2 = file_2[len(base):]

with open("test_1_1.txt","rb") as f:
file_1_1 = f.read()
coll_1_1 = file_1_1[len(file_1):]

with open("test_1_2.txt","rb") as f:
file_1_2 = f.read()
coll_1_2 = file_1_2[len(file_2):]


with open("test_1_1_1.txt","rb") as f:
file_1_1_1 = f.read()
coll_1_1_1 = file_1_1_1[len(file_1_1):]

with open("test_1_1_2.txt","rb") as f:
file_1_1_2 = f.read()
coll_1_1_2 = file_1_1_2[len(file_1_2):]


def w(fn, data):
f = open(fn, "wb")
f.write(data)
f.close()

w("test1.txt", base + coll_1 + coll_1_1 + coll_1_1_1)
w("test2.txt", base + coll_1 + coll_1_1 + coll_1_1_2)
w("test3.txt", base + coll_1 + coll_1_2 + coll_1_1_1)
w("test4.txt", base + coll_1 + coll_1_2 + coll_1_1_2)
w("test5.txt", base + coll_2 + coll_1_1 + coll_1_1_1)
w("test6.txt", base + coll_2 + coll_1_2 + coll_1_1_1)

这样就可以生成6个文件内容不同,但是它们的md5都相同,事实上,你可以生成任意多个

总结

其实很多题目都是类似的,需要我们自己动手写脚本,本质上就是去找hash加密后的字符串前两位是0e,后30位是纯数字的,只要会写脚本,就是万变不离其宗

Reference

本文标题:CTF中关于md5的一些总结

文章作者:xianyu123

发布时间:2020年08月24日 - 14:54

最后更新:2020年09月06日 - 22:15

原始链接:http://0clickjacking0.github.io/2020/08/24/CTF中关于md5的一些总结/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------    本文结束  感谢您的阅读    -------------