要点:
一、更改请求包中的cookie:source为1,则可显示源码,审计源码,可看出本题拿flag思路如下:
1、计算出md5($secret+$admin+ $password),并赋给cookie: getmein,重新发包则可显示flag
2、secret末知(只知长度为15字节),但已知md5($secret+'admin'+'admin'),即响应包中的sample-hash。
3、由于 password不允许赋值为admin 因此需要构造包中的admin和 password的值来绕过secret计算出正确的mac值
4.为达到第3步的要求,可利用hash长度扩展攻击
二、hash长度扩展攻击的原理(以下内容参考https://www.freebuf.com/articles/web/31756.html)
1.该攻击是利用不严谨的mac计算方法来绕过未知的key
2.基本原理知下:
a、md5算法会首先将数据划分为长度为512位的分组,并对各个分组进行操作,对长度不到512位的分组,会首先补齐到512位。补齐规则是:先补齐到448位,补的数是首位为1,余均为0。其次,计算数据长度,并将该长度填充到最后64位,该64位的其余均补0。最终补齐后长度为448+64=512位
b、md5在计算摘要时,会引入四个长度为32位的初始量(称为registers),计算过程为: 对每个512位的分组划分为长度为128位的四个子组。对首个分组,利用registers和预先设定的函数,对四个子组进行复杂的变换后得到128位长的序列。将该序列划分为四个长度为32位的子序列3,作为下一分组的registers,进行同样的操作。以此类推,直到最后一个分组操作完成,最后输出的128位序列即为摘要值
c、由此可见,上一分组得到的摘要即为下一分组的输入
d、长度扩展攻击的原理即是对已知的mac=md5(key,m),通过将key+m人为扩展到512位,后面再加上m1。则服务端计算md5 的过程为:
key + m + 我们填充的扩展值 +
m1 +真实扩展值.
正好与我们添加m1并覆盖registers初始值所计算出来的一样。这是因为攻击者的哈希计算过程,相当于从服务器计算过程的一半紧接着进行下去。
e、为完成攻击,除了人为填充外,还需要对标准md5函数进行修改,将registers修改为我们自己的值,在本题中,即为sample-hash值。同时,由于真实扩展值长度为m1的长度+512,还要对标准函数中的数据长度加上512。最后利用修改过的函数对m1求摘要,该值即等于服务器计算出的摘要。
三、修改标准md5函数
感谢https://blog.csdn.net/u011377996/article/details/86365533的博主提供的md5算法的python代码。修改如下:
1、将sample-hash划分为四个32位的分组,并将每分组按字节倒序(小端序),分别替换原代码中的四个register:A、B、C、D:
A=0xb2801557
B=0x06f3656c
C=0x644f6d37
D=0xc7b53ce5
2、将原代码中计算出的长度加上512:
原代码:length = struct.pack('<Q', len(message)*8)
修改后:length = struct.pack('<Q', len(message)*8+512)
3、运行修改代码,对abc求hash,值为7db18a2831cdab27425f299ca09f034e
四、拿flag
1、构造admin=admin,password=admin+扩展值+abc,由于secret为15字节长,再加上两个admin长度为25字节即200位(16进制的c8),因此到448位应该填充1个\x80、30个\x00,再填充长度为1个\xc8、最后再填充7个\x00
2、因此url转码后,password构造为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%00abc
3、再在请求包头部添加cookie:getmein为刚才计算的hash值7db18a2831cdab27425f299ca09f034e。
4、发送请求,即可返回flag。如图:
五、下面给出修改过的md5代码:
# -*- coding: utf-8 -*-
# Author:0verWatch
import struct
import math
import binascii
lrot = lambda x,n: (x << n)|(x >> 32- n) #循环左移的骚操作
#初始向量
A, B, C, D = (0xb2801557, 0x06f3656c, 0x644f6d37, 0xc7b53ce5)
# A, B, C, D = (0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210)
#循环左移的位移位数
r = [ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
]
#使用正弦函数产生的位随机数,也就是书本上的T[i]
k = [int(math.floor(abs(math.sin(i + 1)) * (2 ** 32))) for i in range(64)]
def init_mess(message):
global A
global B
global C
global D
A, B, C, D = (0xb2801557, 0x06f3656c, 0x644f6d37, 0xc7b53ce5)
# A, B, C, D = (0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210)
length = struct.pack('<Q', len(message)*8+512) #原消息长度64位比特的添加格式,太骚额这种写法
while len(message) > 64:
solve(message[:64])
message = message[64:]
#长度不足64位消息自行填充
message += '\x80'
message += '\x00' * (56 - len(message) % 64)
#print type(length)
message += length
# print binascii.b2a_hex(message)
solve(message[:64])
def solve(chunk):
global A
global B
global C
global D
w = list(struct.unpack('<' + 'I' * 16, chunk)) #分成16个组,I代表1组32位,tql,学到了
a, b, c, d = A, B, C, D
for i in range(64): #64轮运算
if i < 16: #每一轮运算只用到了b,c,d三个
f = ( b & c)|((~b) & d)
flag = i #用于标识处于第几组信息
elif i < 32:
f = (b & d)|(c & (~d))
flag = (5 * i +1) %16
elif i < 48:
f = (b ^ c ^ d)
flag = (3 * i + 5)% 16
else:
f = c ^(b |(~d))
flag = (7 * i ) % 16
tmp = b + lrot((a + f + k[i] + w[flag])& 0xffffffff,r[i]) #&0xffffffff为了类型转换
a, b, c, d = d, tmp & 0xffffffff, b, c
#print(hex(a).replace("0x","").replace("L",""), hex(b).replace("0x","").replace("L","") , hex(c).replace("0x","").replace("L",""), hex(d).replace("0x","").replace("L",""))
A = (A + a) & 0xffffffff
B = (B + b) & 0xffffffff
C = (C + c) & 0xffffffff
D = (D + d) & 0xffffffff
def digest():
global A
global B
global C
global D
return struct.pack('<IIII',A,B,C,D)
def hex_digest():
return binascii.hexlify(digest()).decode()
if __name__ == '__main__':
while True:
mess = raw_input("请输入你的信息:")
init_mess(mess)
out_put = hex_digest()
print out_put