hash长度扩展攻击

hash长度扩展攻击

MD5加密算法

image

分组

首先要知道md5的运算都是将明文分割为以512bit(64字节)一组进行运算的,而最后一组不够512bit另做处理

补位

最后一组将含有两部分有效信息,一是明文%512bit的数据,二是记录的原消息总长(固定占有64位,也就是8个字节),那么其中剩下的位置=512-64-明文%512bit的部分由100000…(在16进制中为800000…)补满
例:image
前面的616263是明文的尾部,800000…是补位的,1800…是明文长度

链接变量

链接变量最开始是ABCD四个初始序列,共128位

A=0x67452301

B=0xefcdab89

C=0x98badcfe

D=0x10325476   

将第一组链接变量与第一组明文进行复杂运算,算出一组新的A,B,C,D的值,如果消息小于512,也就是只需要计算一次,这时候将新的ABCD的值按ABCD的顺序级联,然后输出,就是MD5的值,如果消息大于512的话,就用第一次算的MD5的值进行后面部分的运算算出新的MD5值,以此类推。

长度扩展攻击原理

加入有这么一个情况,有一个需要MD5加密的字符串C由A和B两部分组成,A是未知的(也可以理解为salt)但是我们知道它的长度和MD5值,B是已知的且可控的,那么
我们将B的值构造一下,就可以得到字符串C的值。

引子:

假如A为test(十六进制为0x74657374)
那么我们构造B,使A+B等于512位,且形式与需要补位的最后一组一样

B=800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000   

接着在后面添加上B=0x746573748,此时str将大于512位,md5加密时系统会自动补位为1024位,并分为两组

第一组=74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000

第二组=74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000   

这样程序先计算第一部分,得到ABCD链接变量

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726

第二部分就用第一部分的ABCD链接变量去运算得到新的ABCD链接变量

A=0x226359e5

b=0x99df12eb

C=0x6853f59e

D=0xf5406385  

最后高低位逆序得到MD5值e5596322eb12df999ef55368856340f5

攻击:

现在我们知道A长度是4,MD5值高低位逆序得到的ABCD链接变量是

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726   

这时我们构造

B=%80%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%c8%00%00%00%00%00%00%00test  

这时C大于512位,补位为1024位,其实前512位得到的ABCD我们已经知道,那么如果我们把初始链接变量改为前512位得到的ABCD计算一下0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000
的MD5,发现是e5596322eb12df999ef55368856340f5,这样两个不同的b值得到了一样的MD5值

实例

通过实例更好理解,这是实验吧的让我进去
image
随便输入username和admin抓包
image
将source值改为1发包得到关键源码

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
<?php
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!

$username = $_POST["username"];
$password = $_POST["password"];

if (!empty($_COOKIE["getmein"])) {
//要求username要等于admin,但是password要不等于admin
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
//要求传入的getmein值要等于MD5加密后的salt+username+password
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
//这里规定sample-hash值为MD5加密后的salt+adminadmin
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}
?>

这道题要求我们post传入的username=admin,password!=admin,又要$COOKIE[“getmein”] === md5($secret . urldecode($username . $password)),这里就要用到hash长度扩展攻击
现在我们知道了:
salt的长度是15
salt+adminadmin的MD5值是571580b26c65f306376d4f64e53cb5c7
那么,我们现在就要开始构造password=

admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00admin

使得拼接后的字符串为

012345678901234adminadmin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00admin  

这里利用password将拼接字符串大于512位,系统将自动补位为1024位,前512位的MD5值就是MD5(salt+admin+admin)(这个我们是已知的,就是sample-hash),那么我们将sample—hash的值高低位逆序得到的ABCD链接向量换掉初始ABCD链接向量进行MD5运算(也就是直接运算第二组),最终得到的值就是md5($secret . urldecode($username . $password))
更换初始链接变量进行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
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#include <iostream>


using namespace std;
typedef unsigned int uint;
typedef long long LL;
const int MAXN = 1e6 + 5;
const int mod = 1e9 + 7;

struct MD5 {

typedef void (MD5::*deal_fun)(uint&, uint, uint, uint, uint, uint, uint);//用于定义函数指针数组
string init_str;//数据字符串
uint init_arr[1000];//最终的数据数组{进行扩充处理后的数据}


const static int MAXN = 1e2;

static uint s_state[4];//最开始的默认静态渐变变量

uint state[4];//这个也是默认渐变变量,但是会改变

static uint rolarray[4][4];//位移数组
static uint mN[4][16];//对M数组的处理

uint curM;//当前处理的直接在整个数据中的位置
uint lenZ;//数据的总长{进行扩充处理后的数据总长,这个数是64的倍数}
uint offset;//需要从第几组开始处理
uint Tarr[64];//当前保存的T数组数据
uint Memory[64 + 5];//当前要处理的64个字节数据
uint M[16];//将64个字节数据分为16个数

MD5();
MD5(string str, int noffset);

//数据处理函数
inline uint F(uint X, uint Y, uint Z);
inline uint G(uint X, uint Y, uint Z);
inline uint H(uint X, uint Y, uint Z);
inline uint I(uint X, uint Y, uint Z);

//循环左移函数
uint ROL(uint s, uint ws);

//过程处理函数
inline void FF(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);
inline void GG(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);
inline void HH(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);
inline void II(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);

//生成T数组单个数据的函数
inline uint T(uint i);

//将总数据中的64个字节移到Memory数组中
void data_Init();

//建立M数组
void create_M_arr();

//移动a,b,c,d,规则在前面介绍了
void l_data_change(uint *buf);

//产生T数组
void create_T_arr();

//得到最终MD5值
string get_MD5();

//过程处理
void processing();

};

uint MD5::rolarray[4][4] = {
{ 7, 12, 17, 22 },
{ 5, 9, 14, 20 },
{ 4, 11, 16, 23 },
{ 6, 10, 15, 21 }
};

uint MD5::mN[4][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 },
{ 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2 },
{ 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 }
};

/*
传统渐变变量
0x67452301,
0xefcdab89,
0x98badcfe,
0x10325476
这四个东西是可以根据要求更改的,如果取上述几个数则和经常用的MD5算出的结果是一样的
对了,由于有些数据是静态的,改变之后不会进行需要重新进行复制
*/

uint MD5::s_state[4] = {
0xb2801557,
0x06f3656c,
0x644f6d37,
0xc7b53ce5
};//已经按小端规则反处理哈希值了


MD5::MD5() {}

MD5::MD5(string str, int noffset = 1) {
offset = noffset;
curM = (noffset - 1) * 64;//从0位置处开始处理
init_str = str;//对数据字符串进行处理
lenZ = init_str.length();
memset(init_arr, 0, sizeof(init_arr));

for(int i = 0; i < lenZ; i ++) {
init_arr[i] = str[i];//最终的数据数组进行赋值
}
/*
将数据扩充到取模64个字节等于56个字节
第一个填充0x80,然后就是0x00了
*/
if(lenZ % 64 != 56) init_arr[lenZ ++] = 0x80;
while(lenZ % 64 != 56) {
init_arr[lenZ ++] = 0x00;
}

/*
最后8个字节保存了没扩充钱位数的多少,记住是位数的个数不是字节的个数,同时是按照小端规则
*/
uint lengthbits = init_str.length() * 8;
init_arr[lenZ ++] = lengthbits & 0xff;
init_arr[lenZ ++] = lengthbits >> 8 & 0xff;
init_arr[lenZ ++] = lengthbits >> 16 & 0xff;
init_arr[lenZ ++] = lengthbits >> 24 & 0xff;

//因为uint最多32位所以我们只要考虑四个字节就可以了,虽然实际上要考虑64位,嘿
lenZ += 4;//这步我没读懂!!!


for(int i = 0;i < 4;i ++){
state[i] = s_state[i];//将最开始的默认静态渐变变量赋值给静态渐变变量
}

}

inline uint MD5::F(uint X, uint Y, uint Z) {
return (X & Y) | ((~X) & Z);
}
inline uint MD5::G(uint X, uint Y, uint Z) {
return (X & Z) | (Y & (~Z));
}
inline uint MD5::H(uint X, uint Y, uint Z) {
return X ^ Y ^ Z;
}
inline uint MD5::I(uint X, uint Y, uint Z) {
return Y ^ (X | (~Z));
}
uint MD5::ROL(uint s, uint ws) {
return (s << ws) | (s >> (32 - ws));
}


inline void MD5::FF(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + F(b, c, d) + x + ac, s) + b;
//printf("ff\n");
}

inline void MD5::GG(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + G(b, c, d) + x + ac, s) + b;
//printf("gg\n");
}

inline void MD5::HH(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + H(b, c, d) + x + ac, s) + b;
//printf("hh\n");
}

inline void MD5::II(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + I(b, c, d) + x + ac, s) + b;
//printf("ii\n");
}

//这里前面讲了
inline uint MD5::T(uint i) {
return (uint)((0xffffffff + 1LL) * abs(sin(i)));
}

//取64个字节放在Memory数组中
void MD5::data_Init() {
uint tmp = 0;
for(int i = 0; i < 64; i ++) {
Memory[i] = init_arr[curM + i];
}
curM += 64;//变化位置
}


void MD5::create_T_arr() {
for(int i = 1; i <= 64; i ++) {
Tarr[i - 1] = T(i);
}
}

/*
这里使用了小端将数据存在M数组中,可以稍微思考一下
*/
void MD5::create_M_arr() {
uint tmp = 0;
int cnt = 0;
for(int i = 0; i < 64; i += 4) {
tmp = 0;
for(int j = 3; j >= 0; j --) {
tmp |= Memory[i + j];
if(j == 0) break;
tmp <<= 8;
}
M[cnt ++] = tmp;
}
}

//移动a,b,c,d,最后一个移到第一个
void MD5::l_data_change(uint *buf) {
uint buftmp[4] = {buf[3], buf[0], buf[1], buf[2]};
for(int i = 0; i < 4; i ++) {
buf[i] = buftmp[i];
}
}

void MD5::processing() {
uint statetmp[4];
for(int i = 0; i < 4; i ++) {
statetmp[i] = state[i];
}
/*
这里的处理只是为了更方便的循环
*/
uint * a = &statetmp[0];
uint * b = &statetmp[1];
uint * c = &statetmp[2];
uint * d = &statetmp[3];

/*
产生M数组和T数组
*/
create_M_arr();
create_T_arr();

/*
建立函数指针数组
循环处理
*/

deal_fun d_fun[4] = {
&MD5::FF, &MD5::GG, &MD5::HH, &MD5::II
};

for(int i = 0; i < 4; i ++) {
for(int j = 0; j < 16; j ++) {
(this ->* d_fun[i])(*a, *b, *c, *d, M[mN[i][j]], rolarray[i][j % 4], Tarr[i * 16 + j]);
l_data_change(statetmp);//交换a,b,c,d
}
}


for(int i = 0; i < 4; i ++) {
state[i] += statetmp[i];
}
}

string MD5::get_MD5() {
string result;
char tmp[15];
for(int i = 0;i < (lenZ - (offset - 1) * 64) / 64;i ++){
data_Init();
processing();
}

/*
最终显示也是用小端
*/

for(int i = 0; i < 4; i ++) {
sprintf(tmp, "%02x", state[i] & 0xff);
result += tmp;
sprintf(tmp, "%02x", state[i] >> 8 & 0xff);
result += tmp;
sprintf(tmp, "%02x", state[i] >> 16 & 0xff);
result += tmp;
sprintf(tmp, "%02x", state[i] >> 24 & 0xff);
result += tmp;
}
return result;
}

int main() {
MD5 md1("123456789123456adminadmin123456789123456789123456789123456789123admin",2);
cout << md1.get_MD5() << endl;
return 0;
}

将运行代码得到的hash值利用getmein写入cookie,将username=admin,passsword=admin%80%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%c8%00%00%00%00%00%00%00admin
image
flag:CTF{cOOkieS_4nd_hAshIng_G0_w3LL_t0g3ther}

参考文章:
MD5的Hash长度扩展攻击
科普哈希长度扩展攻击

文章目录
  1. 1. hash长度扩展攻击
    1. 1.1. MD5加密算法
      1. 1.1.1. 分组
      2. 1.1.2. 补位
      3. 1.1.3. 链接变量
    2. 1.2. 长度扩展攻击原理
      1. 1.2.1. 引子:
      2. 1.2.2. 攻击:
  2. 2. 实例
,