弱比较类型

  • php中的等比较运算符有两种,=====

    • ==:先将左右两边类型转化成一致,再进行比较值是否一致
    • ===:先比较左右类型是否一致,若一致再比较值是否一致
  • 字符串与数字比较

    1
    2
    3
    var_dump('a' == 0);	//bool(true)
    var_dump('1a' == 1); //bool(true)
    var_dump('12a' == 1); //bool(false)

    字符串在和数字比较的时候会将字符串转化为数字,如果字符串是以数字开头的,则会转换成数字再做比较,纯字符串则转换失败成false

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $key = $_GET['key'];
    if(!is_numeric($key)) {
    exit("Just num!");
    }
    $key = intval($key);
    $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
    if($key == $str) {
    echo $flag;
    }

    构造payload:

    1
    ?key=123
  • 布尔值与任意值比较

    1
    2
    3
    var_dump(True == 0);	//bool(false)
    var_dump(True == 'False'); //bool(true)
    var_dump(True == 2); //bool(true)
  • hash值和字符串“0”比较

    因为当hash开头为0e后全为数字的话,进行比较时就会将其当做科学计数法来计算,用计算出的结果来进行比较。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $str1 = "a";
    echo md5($str1); //0cc175b9c0f1b6a831c399e269772661
    var_dump(md5($str1) == '0'); //bool(false)


    $str2 = "s224534898e";
    echo md5($str2); //0e420233178946742799316739797882
    var_dump(md5($str2) == '0'); //bool(true)


    $str3 = 'a1b2edaced';
    echo md5($str3); //0e45ea817f33691a3dd1f46af81166c4bool
    var_dump(md5($str3) == '0'); //bool(false)


    var_dump('0e111111111111' == '0'); //bool(true)

    第二、四条由于0e后全为数字,按科学计数法即0乘e的幂次方为0,所以比较为true

绕过

strcmp函数绕过

1
2
3
4
5
6
7
8
9
<?php
$flag = "flag{xxxxx}";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0)
die('Flag: '.$flag);
else
print 'No';
}
?>

strcmp(string1,string2),该函数返回:

0 - 如果两个字符串相等

<0 - 如果 string1 小于 string2

>0 - 如果 string1 大于 string2

对于传入非字符串类型的数据的时候,strcmp函数会报错,将返回0。所以,strcmp()在比较字符串和数组的时候直接返回0,这样通过把目标变量设置成数组就可以绕过该函数的限制,构建payload:

1
?a[]=1

MD5/sha1弱类型绕过

  • 0e绕过

    弱比较会把0exxxx当做科学计数法,不管后面的值为任何东西,0的任何次幂都为0

    一些字符串md5值以0e开头:

    1
    2
    3
    4
    5
    6
    QNKCDZO
    240610708
    s878926199a
    s155964671a
    s21587387a
    0e215962017

    sha1值以0e开头:

    1
    2
    3
    4
    5
    6
    10932435112: 0e07766915004133176347055865026311692244
    aaroZmOk: 0e66507019969427134894567494305185566735
    aaK1STfY: 0e76658526655756207688271159624026011393
    aaO8zKZF: 0e89257456677279068558073954252716165668
    aa3OFF9m: 0e36977786278517984959260394024281014729
    0e1290633704: 0e19985187802402577070739524195726831799
  • 数组绕过

    md5()函数计算的是一个字符串的哈希值,对于数组则返回false,如果是md5((string)$apple) == md5((string)$banana))则不能用数组,因为转换成字符串后都为NULL

MD5/sha1强类型绕过

因为强类型比较,不仅比较值,还比较类型,0e会被当做字符串,所以不能用0e来进行
但是我们可以用MD值完全相同的字符来进行绕过。

  • 数组绕过

    方法同上

  • MD5强碰撞

    即找到两个不同输入产生相同哈希值的过程。

    如:

    1
    2
    a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
    b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
    1
    2
    a=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
    b=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
    1
    2
    a=0e306561559aa787d00bc6f70bbdfe3404cf03659e704f8534c00ffb659c4c8740cc942feb2da115a3f4155cbb8607497386656d7d1f34a42059d78f5a8dd1ef
    b=0e306561559aa787d00bc6f70bbdfe3404cf03659e744f8534c00ffb659c4c8740cc942feb2da115a3f415dcbb8607497386656d7d1f34a42059d78f5a8dd1ef

特定条件下的MD验证绕过:ffifdyop

1
Select * from ’admin’ where password=md5($pass,true)

即MD5报文将以原始 16字符二进制格式返回

ffifdyop 字符串经过MD5加密后为276f722736c95d99e921722cf9ed621c
再转换成字符串为’or’6乱码

Select * from ’admin’ where password=‘or’6乱码 相当于万能密码

extract变量覆盖

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan)) {
$content=trim(file_get_contents($flag));
if($shiyan==$content) {
echo'flag{xxx}';
} else {
echo'Oh.no';
}
}
?>

extract():函数从数组中将变量导入到当前的符号表
trim(string[charlist]):移除字符串两侧的空白字符或其他预定义字符,若省略后面一个参数,则去除\0\t\n\x0B\r和空格。

运用extract()将GET方式获得的变量导入到当前的符号表中,然后判断$flag和$shiyan两个变量的内容是否相等。那么我们将$flag和$shiyan这两个变量的内容都会被设置成空字符串。构建payload:

1
Payload:?flag=&shiyan=

限制传入的匹配

1
2
3
4
5
6
7
8
9
10
11
<?php
$flag = "flag";
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
echo 'You password must be alphanumeric';
else if (strpos ($_GET['password'], '--') !== FALSE) //判断是否包含-,!==既比较值也比较类型
die('Flag: ' . $flag);
else
echo 'Invalid password';
}
?>

ereg():限制传入内容,例如上面就是限制了只能传入数字以及大小写字母。
strpos():查找字符串在另一字符串中第一次出现的位置。

对传入进行限制,但是又要求密码中含有–,因此我们这里有两种绕过方法:

  • 数组法

    同样strpos()如果传入数组,会返回NULL,从而绕过对--的检测,构建payload:

    1
    ?password[]=1
  • 截断法

    %00后的函数无法识别,因此构建payload:

    1
    ?password=1%00--

JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>

输入一个数组进行json解码,解码后的message与key值相同才会得到flag,使用弱类型进行绕过,key肯定是字符串,两个等号时会转化成同一类型再进行比较,直接构造一个0就可以相等了,通过0==”admin”这种形式绕过,构建payload:

1
?message={"key":0}

长度限制绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$flag = "xxx";
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) {
echo 'You password must be alphanumeric';
} else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) {
if (strpos ($_GET['password'], '-') !== FALSE) //strpos — 查找字符串首次出现的位置 {
die('Flag: ' . $flag);
} else {
echo('have not been found');
}
} else {
echo 'Invalid password';
}
}
?>

要求输入password的长度小于8位,且值要大于9999999,并且需要匹配到-,这里就要用到科学计数法了:

  • 1e10 = 10^10

而匹配-可用截断法,所以构造payload:

1
?password=1e10%00-

urldecode二次编码绕过

1
2
3
4
5
6
7
8
9
10
11
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("not allowed!");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ") {
echo "Access granted!";
echo "flag";
}
?>

使用GET传参时,浏览器就已经把hakerDJ进行了一次解码了,然后又用了urldecode函数又再次进行了一次解码。所以我们要将hakerDJ进行二次编码:

1
Payload: ?id=%25%36%38%25%36%31%25%36%33%25%36%42%25%36%35%25%37%32%25%34%34%25%34%41

$a==md5($a),md5($a)==md5(md5($a))

0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等:

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)

MD5和双MD5都是0e开头的:

1
2
3
CbDLytmyGm2xQyaLNhWn
770hQgrBOjrcqftrlaZk
7r4lGXCH2Ksu2JNT3BYM

哈希长度扩展攻击

概念

哈希长度扩展攻击,简单的说就是由于hash的生成机制使得我们可以人为的在原先明文基础上添加新的拓展字符,从而使得原本的加密链变长,进一步控制加密链的最后一节,使得我们得以控制最终的结果。

为了更好的解释该原理,下面以MD5算法举个例子

MD5加密

MD5

MD5算法是典型的一种信息摘要算法,它是由md2、md3和md4演变而来的。无论是哪一种的md算法都是将一个任意长度的字符串加密为一串固定长度的密文。在这整个加密过程中,会将明文字符串转换为一个128位的消息摘要,接直把消息摘要转换为一个十六进制的字符串就会得到32位的字符串,也就是我们平时见到的MD5密文

因为MD5加密过程经过了压缩,加密和hash算法,所以MD5加密的内容是不可逆的

MD5算法过程

要了解算法的原理过程,肯定需要一个流程图便于理解:

大致步骤为:

  1. 把消息分为n个分组
  2. 对最后一个消息分组进行填充
  3. 和输入量进行运算,运算结果位下一个分组的输入量
  4. 输出最终结果

下面举个例子好理解一点:

比如我们需要对1234567890123456进行加密,首先我们需要将它转化为二进制,即:

1
#转换结果 00110001 00110010 00110011 00110100 00110101 00110110 00110111 00111000 00111001 00110000 00110001 00110010 00110011 00110100 00110101 00110110

因为一个ASCIl码对应8位二进制字符,所以我们得到了128位二进制字符,下面我们在winhex(需要转为十六进制)上观察:

对于MD5算法来说,我们需要将原数据进行分块处理,以512位个二进制数据为一块,而最后一块的处理分为以下两种情况:

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

对于padding数据(长度不定)来说:首位二进制位1,其余位为0.

对于我们的数据只有128位二进制字符,所以我们需要填充到448位,又因为**10000000(2)=80(16),**补充上去即可:

QQ截图20240814152802

对于长度信息位(长度8Byte=64bit)来说,我们还需补位8byte,从低位走向高位,原始明文为1234567890123456共16byte,有16*8=128bit,转换为十六进制为80,写入倒数第八个byte位,之后补7byte的0x00:

接下来需要用这64byte的数据进行计算,与初始向量进行计算

计算信息的摘要需要用补位结果的数据进行运算,也就是补位后的512bit的消息,在计算时候有个初始的向量,这里初始的向量是一个固定的值:

1
2
3
4
A 01 23 45 67 0x67452301
B 89 AB CD EF 0xEFCDAB89
C FE DC BA 98 0x98BADCFE
D 76 54 32 10 0x10325476

由于在计算机存储中采用的是小端存储方式,所以上面初始化向量在程序中的初始化代码为后面的0x部分。

然后将刚才的512bit消息和初始化向量进行第一轮的运算,之后初始化向量会被新的值覆盖,最后一轮的向量经过高低位互换后就是计算出的MD5值。

1
2
高低位互换:
abcdef->fedcba

高低位互换的过程如下:

1
2
3
4
5
6
7
8
9
10
假如最后一轮的运算后的向量值为:
A=0xabcdef12
B=0xabcdef12
C=0xabcdef12
D=0xabcdef12
那么进行高低位互换之后得到的数值为:
12 ef cd ab
12 ef cd ab
12 ef cd ab
12 ef cd ab

进行拼接得到最后加密结果
12efcdab12efcdab12efcdab12efcdab

攻击工具

上面我们也了解到了MD5的加密过程,而我们只要知道一个hash值,知道原来数据的数据长度,那么我们就可以算出
原数据+填充数据到512+任意内容的hash值。

而有些ctf就是需要我们找填充的数据及填充后的hash值,当然我们不可能手搓,下面是关于该攻击的一个脚本

脚本

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# -*- coding: utf-8 -*-
# @Author: King kaki
# @Date: 2018-08-04 12:40:11
# @Last Modified by: kingkk
# @Last Modified time: 2018-08-12 15:08:28
import math


F = lambda x, y, z: ((x & y) | ((~x) & z))
G = lambda x, y, z: ((x & z) | (y & (~z)))
H = lambda x, y, z: (x ^ y ^ z)
I = lambda x, y, z: (y ^ (x | (~z)))
L = lambda x, n: (((x << n) | (x >> (32 - n))) & (0xffffffff))
shi_1 = (7, 12, 17, 22) * 4
shi_2 = (5, 9, 14, 20) * 4
shi_3 = (4, 11, 16, 23) * 4
shi_4 = (6, 10, 15, 21) * 4
m_1 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
m_2 = (1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12)
m_3 = (5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2)
m_4 = (0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9)


def T(i):
return (int(4294967296 * abs(math.sin(i)))) & 0xffffffff


def shift(shift_list):
shift_list = [shift_list[3], shift_list[0], shift_list[1], shift_list[2]]
return shift_list


def fun(fun_list, f, m, shi):
count = 0
global Ti_count
while count < 16:
xx = int(fun_list[0], 16) + f(int(fun_list[1], 16), int(fun_list[2], 16), int(fun_list[3], 16)) + int(m[count], 16) + T(Ti_count)
xx &= 0xffffffff
ll = L(xx, shi[count])
fun_list[0] = hex((int(fun_list[1], 16) + ll) & 0xffffffff)
fun_list = shift(fun_list)
count += 1
Ti_count += 1
return fun_list


def gen_m16(order, ascii_list, f_offset):
ii = 0
m16 = [0] * 16
f_offset *= 64
for i in order:
i *= 4
m16[ii] = '0x' + ''.join((ascii_list[i + f_offset] + ascii_list[i + 1 + f_offset] + ascii_list[i + 2 + f_offset] + ascii_list[i + 3 + f_offset]).split('0x'))
ii += 1
for ind in range(len(m16)):
m16[ind] = reverse_hex(m16[ind])
return m16


def reverse_hex(hex_str):
hex_str = hex_str[2:]
if len(hex_str) < 8:
hex_str = '0' * (8 - len(hex_str)) + hex_str
hex_str_list = []
for i in range(0, len(hex_str), 2):
hex_str_list.append(hex_str[i:i + 2])
hex_str_list.reverse()
hex_str_result = '0x' + ''.join(hex_str_list)
return hex_str_result


def show_result(f_list):
result = ''
f_list1 = [0] * 4
for i in f_list:
f_list1[f_list.index(i)] = reverse_hex(i)[2:]
result += f_list1[f_list.index(i)]
return result


def padding(input_m, msg_lenth=0):
ascii_list = list(map(hex, map(ord, input_m)))
msg_lenth += len(ascii_list) * 8
ascii_list.append('0x80')
for i in range(len(ascii_list)):
if len(ascii_list[i]) < 4:
ascii_list[i] = '0x' + '0' + ascii_list[i][2:]
while (len(ascii_list) * 8 + 64) % 512 != 0:
ascii_list.append('0x00')
msg_lenth_0x = hex(msg_lenth)[2:]
msg_lenth_0x = '0x' + msg_lenth_0x.rjust(16, '0')
msg_lenth_0x_big_order = reverse_hex(msg_lenth_0x)[2:]
msg_lenth_0x_list = []
for i in range(0, len(msg_lenth_0x_big_order), 2):
msg_lenth_0x_list.append('0x' + msg_lenth_0x_big_order[i: i + 2])
ascii_list.extend(msg_lenth_0x_list)
return ascii_list


def md5(input_m):
global Ti_count
Ti_count = 1
abcd_list = ['0x67452301', '0xefcdab89', '0x98badcfe', '0x10325476']
ascii_list = padding(input_m)
for i in range(0, len(ascii_list) // 64):
aa, bb, cc, dd = abcd_list
order_1 = gen_m16(m_1, ascii_list, i)
order_2 = gen_m16(m_2, ascii_list, i)
order_3 = gen_m16(m_3, ascii_list, i)
order_4 = gen_m16(m_4, ascii_list, i)
abcd_list = fun(abcd_list, F, order_1, shi_1)
abcd_list = fun(abcd_list, G, order_2, shi_2)
abcd_list = fun(abcd_list, H, order_3, shi_3)
abcd_list = fun(abcd_list, I, order_4, shi_4)
output_a = hex((int(abcd_list[0], 16) + int(aa, 16)) & 0xffffffff)
output_b = hex((int(abcd_list[1], 16) + int(bb, 16)) & 0xffffffff)
output_c = hex((int(abcd_list[2], 16) + int(cc, 16)) & 0xffffffff)
output_d = hex((int(abcd_list[3], 16) + int(dd, 16)) & 0xffffffff)
abcd_list = [output_a, output_b, output_c, output_d]
Ti_count = 1
print(ascii_list)
return show_result(abcd_list)


# md5-Length Extension Attack: 计算 md5(message + padding + suffix), res = md5(message), len_m = len(message)
def md5_lea(suffix, res, len_m):
global Ti_count
Ti_count = 1
abcd_list = []
for i in range(0, 32, 8):
abcd_list.append(reverse_hex('0x' + res[i: i + 8]))
# print(abcd_list)
ascii_list = padding(suffix, (len_m + 72) // 64 * 64 * 8) # len(message + padding) * 8
# print(ascii_list)
for i in range(0, len(ascii_list) // 64):
aa, bb, cc, dd = abcd_list
order_1 = gen_m16(m_1, ascii_list, i)
order_2 = gen_m16(m_2, ascii_list, i)
order_3 = gen_m16(m_3, ascii_list, i)
order_4 = gen_m16(m_4, ascii_list, i)
abcd_list = fun(abcd_list, F, order_1, shi_1)
abcd_list = fun(abcd_list, G, order_2, shi_2)
abcd_list = fun(abcd_list, H, order_3, shi_3)
abcd_list = fun(abcd_list, I, order_4, shi_4)
output_a = hex((int(abcd_list[0], 16) + int(aa, 16)) & 0xffffffff)
output_b = hex((int(abcd_list[1], 16) + int(bb, 16)) & 0xffffffff)
output_c = hex((int(abcd_list[2], 16) + int(cc, 16)) & 0xffffffff)
output_d = hex((int(abcd_list[3], 16) + int(dd, 16)) & 0xffffffff)
abcd_list = [output_a, output_b, output_c, output_d]
Ti_count = 1
# print(ascii_list)
return show_result(abcd_list)

def url_append(hex_bit):
len_append = '0x{}{}'.format( (18-len(hex_bit))*'0', hex_bit[2:])
len_append = reverse_hex(len_append)[2:]
# print(len_append)
t = ''
for i in range(len(len_append)):
if i % 2 ==0 :
t += '%'+len_append[i:i+2]
else:
pass
return t

if __name__ == '__main__':
'''
修改res为已知哈希值
extend 为拓展值
自动遍历出1-30长度的payload url编码表达式
'''
res = '1c3de59d2f68788cc792e0eb7d604710'
extend = '}'
# print(reverse_hex('0x' + res))

for i in range(45):
hex_bit = hex(i*8)
t = url_append(hex_bit)
print('[%d]' % i,md5_lea(extend,res,i))
# print('{}%80{}{}{}'.format('X'*i, (55-i)*'%00',t, extend) )
print('%80{}{}{}'.format((55-i)*'%00',t, extend) )
# print('{}{}'.format( hex(i), (18-len(hex(i)))*'0') )
# from urllib.parse import unquote
# print(md5_lea('kingkk','571580b26c65f306376d4f64e53cb5c7',10))

项目

hashpump

hashpump是一个专门生成MD5长度拓展攻击payload的工具。

1
Github仓库:https://github.com/bwall/HashPump

现在被作者删了找不到了

hash-ext-attack

推荐中文项目:https://github.com/shellfeel/hash-ext-attack

MD5前缀构造法

工具:fastcoll

原理是构造两个不一样的字符串,但是MD5是一样

通过构造前缀碰撞法”(chosen-prefix collisions)来进行哈希碰撞(也可以构造两个MD5相同的文件):

1
2
fastcoll_v1.0.0.5.exe -p 2.pdf -o 3.pdf 4.pdf
fastcoll_v1.0.0.5.exe -p test.txt -o 3.txt 4.txt

文件内的内容为你想构造的前缀

输出两个txt文本后:在php环境中运行Md5collision.php

可以看到,二进制的hash一样。 但是实际内容不一样。

Md5collision.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
function readmyfile($path){
$fh = fopen($path, "rb");
$data = fread($fh, filesize($path));
fclose($fh);
return $data;
}
echo '¶þ½øÖÆhash '. md5( (readmyfile("3.txt")));
echo "<br><br>\r\n";
echo 'URLENCODE '. urlencode(readmyfile("3.txt"));
echo "<br><br>\r\n";
echo 'URLENCODE hash '.md5(urlencode (readmyfile("3.txt")));
echo "<br><br>\r\n";
echo '¶þ½øÖÆhash '.md5( (readmyfile("4.txt")));
echo "<br><br>\r\n";
echo 'URLENCODE '. urlencode(readmyfile("4.txt"));
echo "<br><br>\r\n";
echo 'URLENCODE hash '.md5( urlencode(readmyfile("4.txt")));
echo "<br><br>\r\n";
if($_POST['param1']!==$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])){
die("success!");
}

可以看到前面相同的内容是我们构造的前缀,最后形成了两个字符串不同但二进制哈希值相同的字符串

适用题型

适用于字符串拼接类型,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__file__);

$master = "MD5 master!";

if(isset($_POST["master1"]) && isset($_POST["master2"])){
if($master.$_POST["master1"] !== $master.$_POST["master2"] && md5($master.$_POST["master1"]) === md5($master.$_POST["master2"])){
echo $master . "<br>";
echo file_get_contents('/flag');
}
}
else{
die("master? <br>");
}

NAN和INF

NAN和INF,分别为非数值和无穷大。当一个运算无法计算结果时,就会产生NAN,可以通过0/0产生;而当数值超过PHP_FLOAT_MAX或者使用一些数学函数产生极大或极小的结果时,就会得到INF。

但是var_dump它们数据类型却是double

那么在MD5函数处理他们时,是将其直接转化为字符串”NAN”和字符串”INF”使用的,但他们有个特殊的性质,他们与任何数据类型(除了true)做强类型或弱类型比较均为false,甚至NAN===NAN都是false(INF===INF为true),但md5('NAN')===md5('NAN')和md5('INF')===md5('INF')为true.

1
2
3
4
5
6
<?php
error_reporting(0);
highlight_file(__FILE__);

var_dump(md5('INF')===md5('INF')); //bool(true)
var_dump(md5('NAN')===md5('NAN')); //bool(true)

截断比较

md5截断爆破

1
substr(md5(?),0,5)==='8ffb1'

爆破脚本:

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
import hashlib
from multiprocessing.dummy import Pool as ThreadPool

# MD5截断数值已知 求原始数据
# 例子 substr(md5(captcha), 0, 6)=60b7ef

def md5(s): # 计算MD5字符串
return hashlib.md5(str(s).encode('utf-8')).hexdigest()


keymd5 = '8ffb1' #已知的md5截断值
md5start = 0 # 设置题目已知的截断位置
md5length = 5

def findmd5(sss): # 输入范围 里面会进行md5测试
key = sss.split(':')
start = int(key[0]) # 开始位置
end = int(key[1]) # 结束位置
result = 0
for i in range(start, end):
# print(md5(i)[md5start:md5length])
if md5(i)[0:5] == keymd5: # 拿到加密字符串
result = i
print(result) # 打印
break


list=[] # 参数列表
for i in range(10): # 多线程的数字列表 开始与结尾
list.append(str(10000000*i) + ':' + str(10000000*(i+1)))
pool = ThreadPool() # 多线程任务
pool.map(findmd5, list) # 函数 与参数列表
pool.close()
pool.join()

sha256截断爆破

1
substr(sha256(?),0,5)==='8ffb1'

爆破脚本:

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
import hashlib
from multiprocessing.dummy import Pool as ThreadPool

# sha256截断数值已知 求原始数据
# 例子 substr(sha256(captcha), 0, 6)=60b7ef

def sha256(s): # 计算sha256字符串
return hashlib.sha256(('TQLCTF'+str(s)).encode('utf-8')).hexdigest()


keysha256 = '5625f' #已知的sha256截断值
sha256start = 0 # 设置题目已知的截断位置
sha256length = 5

def findsha256(sss): # 输入范围 里面会进行sha256测试
key = sss.split(':')
start = int(key[0]) # 开始位置
end = int(key[1]) # 结束位置
result = 0
for i in range(start, end):
# print(sha256(i)[sha256start:sha256length])
if sha256(i)[0:5] == keysha256: # 拿到加密字符串
result = i
print(result) # 打印
break


list=[] # 参数列表
for i in range(10): # 多线程的数字列表 开始与结尾
list.append(str(10000000*i) + ':' + str(10000000*(i+1)))
pool = ThreadPool() # 多线程任务
pool.map(findsha256, list) # 函数 与参数列表
pool.close()
pool.join()