哈希扩展长度攻击
===
本文写于2017年12月5日
弄了2天才弄出来
0x00 起于一道CTF
这是一道好题!学了一种新的攻击方法,顺带知道了hash的实现。只不过现在这种攻击方法也只活在ctf中了
首先是改包能够看到源码
flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!
$username = $_POST["username"];
$password = $_POST["password"];
if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
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.");}}
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}}
要显示出flag就得要通过这个逻辑:$COOKIE["getmein"] === md5($secret . urldecode($username . $password)
问题在于不知道secret的值是多少,爆破几乎是不可能的,这里就要用到哈希扩展长度攻击
# 0x01什么是哈希扩展长度攻击
已知:salt的MD5值,但是不知道str1本身的值
我们可以知道salt+padding+任意字符 的md5值
因为md5的实现中64轮变换每次都是用的上次变换的结果,通过md5可以反向算出最后一次变换的四个值,之后加入到变换函数中,在后面添加任意数据。这样在不知道salt本身值的情况下也能算出salt+padding+任意数据的md5值了
我们需要知道md5的实现过程才能理解这种攻击方式
## hash的实现
首先对消息进行分组,每组64个字节,不足64个字节的部分用padding补齐,其中,第一个补充的字节是0x80,之后的都是0x00,padding的最后8个字节用来表示需要哈希的消息长度
defpadding(self,n,sz=None):
ifszisNone:
sz=n
pre=64-8
sz=struct.pack("Q",sz*8)
pad='\x80'
n+=1
ifn%64<=pre:
pad+='\x00'*(pre-n%64)
pad+=sz
else:
pad+='\x00'*(pre+64-n%64)
pad+=sz
returnpad
#最后八个字节为数据长度
defpad(self,msg):
returnmsg+self.padding(len(msg))
之后开始对每组消息进行压缩,经过64轮的数学变换(位运算的与或非异或、按位左移右移、按位无符号左移右移、进制转换、sin、和0xFF、0xFFFFFFF做与运算保证位数不变等等)每一次变换压缩的结果被当成下一轮变换压缩的初始值。最开始的初始值是由文档规定好的。最后的结果拼在一起就是128位的md5值
四个定义好的变换函数:
F(X,Y,Z) =(X&Y)|((~X)&Z)
G(X,Y,Z) =(X&Z)|(Y&(~Z))
H(X,Y,Z) =X^Y^Z
I(X,Y,Z)=Y^(X|(~Z))
(&是与,|是或,~是非,^是异或)
四个定义好的初始值:
A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。
变换函数
defXX(func,a,b,c,d,x,s,ac):
res=0L
res=res+a+func(b,c,d)
res=res+x
res=res+ac
res=res&0xffffffffL
res=_rotateLeft(res,s)
res=res&0xffffffffL
res=res+b
returnres&0xffffffffL
defmd5_compress(self,buf):
iflen(buf)!=64:
raiseValueError,"Invalidbufferoflength%d:%s"%(len(buf),repr(buf))
inp=struct.unpack("I"*16,buf)
a,b,c,d=self.A,self.B,self.C,self.D
#开始进行64轮数学变换,四大轮,每轮16次
#Round1.
S11,S12,S13,S14=7,12,17,22
a=XX(F,a,b,c,d,inp[0],S11,0xD76AA478L)#1
d=XX(F,d,a,b,c,inp[1],S12,0xE8C7B756L)#2
c=XX(F,c,d,a,b,inp[2],S13,0x242070DBL)#3
b=XX(F,b,c,d,a,inp[3],S14,0xC1BDCEEEL)#4
a=XX(F,a,b,c,d,inp[4],S11,0xF57C0FAFL)#5
d=XX(F,d,a,b,c,inp[5],S12,0x4787C62AL)#6
c=XX(F,c,d,a,b,inp[6],S13,0xA8304613L)#7
b=XX(F,b,c,d,a,inp[7],S14,0xFD469501L)#8
a=XX(F,a,b,c,d,inp[8],S11,0x698098D8L)#9
d=XX(F,d,a,b,c,inp[9],S12,0x8B44F7AFL)#10
c=XX(F,c,d,a,b,inp[10],S13,0xFFFF5BB1L)#11
b=XX(F,b,c,d,a,inp[11],S14,0x895CD7BEL)#12
a=XX(F,a,b,c,d,inp[12],S11,0x6B901122L)#13
d=XX(F,d,a,b,c,inp[13],S12,0xFD987193L)#14
c=XX(F,c,d,a,b,inp[14],S13,0xA679438EL)#15
b=XX(F,b,c,d,a,inp[15],S14,0x49B40821L)#16
#Round2.
S21,S22,S23,S24=5,9,14,20
a=XX(G,a,b,c,d,inp[1],S21,0xF61E2562L)#17
d=XX(G,d,a,b,c,inp[6],S22,0xC040B340L)#18
c=XX(G,c,d,a,b,inp[11],S23,0x265E5A51L)#19
b=XX(G,b,c,d,a,inp[0],S24,0xE9B6C7AAL)#20
a=XX(G,a,b,c,d,inp[5],S21,0xD62F105DL)#21
d=XX(G,d,a,b,c,inp[10],S22,0x02441453L)#22
c=XX(G,c,d,a,b,inp[15],S23,0xD8A1E681L)#23
b=XX(G,b,c,d,a,inp[4],S24,0xE7D3FBC8L)#24
a=XX(G,a,b,c,d,inp[9],S21,0x21E1CDE6L)#25
d=XX(G,d,a,b,c,inp[14],S22,0xC33707D6L)#26
c=XX(G,c,d,a,b,inp[3],S23,0xF4D50D87L)#27
b=XX(G,b,c,d,a,inp[8],S24,0x455A14EDL)#28
a=XX(G,a,b,c,d,inp[13],S21,0xA9E3E905L)#29
d=XX(G,d,a,b,c,inp[2],S22,0xFCEFA3F8L)#30
c=XX(G,c,d,a,b,inp[7],S23,0x676F02D9L)#31
b=XX(G,b,c,d,a,inp[12],S24,0x8D2A4C8AL)#32
#Round3.
S31,S32,S33,S34=4,11,16,23
a=XX(H,a,b,c,d,inp[5],S31,0xFFFA3942L)#33
d=XX(H,d,a,b,c,inp[8],S32,0x8771F681L)#34
c=XX(H,c,d,a,b,inp[11],S33,0x6D9D6122L)#35
b=XX(H,b,c,d,a,inp[14],S34,0xFDE5380CL)#36
a=XX(H,a,b,c,d,inp[1],S31,0xA4BEEA44L)#37
d=XX(H,d,a,b,c,inp[4],S32,0x4BDECFA9L)#38
c=XX(H,c,d,a,b,inp[7],S33,0xF6BB4B60L)#39
b=XX(H,b,c,d,a,inp[10],S34,0xBEBFBC70L)#40
a=XX(H,a,b,c,d,inp[13],S31,0x289B7EC6L)#41
d=XX(H,d,a,b,c,inp[0],S32,0xEAA127FAL)#42
c=XX(H,c,d,a,b,inp[3],S33,0xD4EF3085L)#43
b=XX(H,b,c,d,a,inp[6],S34,0x04881D05L)#44
a=XX(H,a,b,c,d,inp[9],S31,0xD9D4D039L)#45
d=XX(H,d,a,b,c,inp[12],S32,0xE6DB99E5L)#46
c=XX(H,c,d,a,b,inp[15],S33,0x1FA27CF8L)#47
b=XX(H,b,c,d,a,inp[2],S34,0xC4AC5665L)#48
#Round4.
S41,S42,S43,S44=6,10,15,21
a=XX(I,a,b,c,d,inp[0],S41,0xF4292244L)#49
d=XX(I,d,a,b,c,inp[7],S42,0x432AFF97L)#50
c=XX(I,c,d,a,b,inp[14],S43,0xAB9423A7L)#51
b=XX(I,b,c,d,a,inp[5],S44,0xFC93A039L)#52
a=XX(I,a,b,c,d,inp[12],S41,0x655B59C3L)#53
d=XX(I,d,a,b,c,inp[3],S42,0x8F0CCC92L)#54
c=XX(I,c,d,a,b,inp[10],S43,0xFFEFF47DL)#55
b=XX(I,b,c,d,a,inp[1],S44,0x85845DD1L)#56
a=XX(I,a,b,c,d,inp[8],S41,0x6FA87E4FL)#57
d=XX(I,d,a,b,c,inp[15],S42,0xFE2CE6E0L)#58
c=XX(I,c,d,a,b,inp[6],S43,0xA3014314L)#59
b=XX(I,b,c,d,a,inp[13],S44,0x4E0811A1L)#60
a=XX(I,a,b,c,d,inp[4],S41,0xF7537E82L)#61
d=XX(I,d,a,b,c,inp[11],S42,0xBD3AF235L)#62
c=XX(I,c,d,a,b,inp[2],S43,0x2AD7D2BBL)#63
b=XX(I,b,c,d,a,inp[9],S44,0xEB86D391L)#64
self.A=(self.A+a)&0xffffffffL
self.B=(self.B+b)&0xffffffffL
self.C=(self.C+c)&0xffffffffL
self.D=(self.D+d)&0xffffffffL
# 0x02回到题目
这个时候我们通过脚本构造payload,padding的值得是64位
脚本如下
### coding:utf-8
#这是个哈希扩展攻击的payload生成脚本
#python 本文件名字.py salt的md5 附加的值 salt的长度
import sys
import struct
import hashlib
import binascii
#每轮中用到的数学变换函数。L为循环左移,
def F(x, y, z):
return (x & y) | ((~x) & z)
def G(x, y, z):
return (x & z) | (y & (~z))
def H(x, y, z):
return x ^ y ^ z
def I(x, y, z):
return y ^ (x | (~z))
#注意左移之后可能会超过32位,所以要和0xffffffff做与运算,确保结果为32位。
def _rotateLeft(x, n):
return (x << n) | (x >> (32 - n))
#变换函数
def XX(func, a, b, c, d, x, s, ac):
res = 0L
res = res + a + func(b, c, d)
res = res + x
res = res + ac
res = res & 0xffffffffL
res = _rotateLeft(res, s)
res = res & 0xffffffffL
res = res + b
return res & 0xffffffffL
### 主类
class md5():
#初始化向量,这是标准里定死的
def __init__(self):
self.A, self.B, self.C, self.D = (0x67452301L, 0xefcdab89L, 0x98badcfeL, 0x10325476L)
def md5_compress(self, buf):
if len(buf) != 64:
raise ValueError, "Invalid buffer of length %d: %s" % (len(buf), repr(buf))
inp = struct.unpack("I" * 16, buf)
a, b, c, d = self.A, self.B, self.C, self.D
#开始进行64轮数学变换,四大轮,每轮16次
# Round 1.
S11, S12, S13, S14 = 7, 12, 17, 22
a = XX(F, a, b, c, d, inp[0], S11, 0xD76AA478L) # 1
d = XX(F, d, a, b, c, inp[1], S12, 0xE8C7B756L) # 2
c = XX(F, c, d, a, b, inp[2], S13, 0x242070DBL) # 3
b = XX(F, b, c, d, a, inp[3], S14, 0xC1BDCEEEL) # 4
a = XX(F, a, b, c, d, inp[4], S11, 0xF57C0FAFL) # 5
d = XX(F, d, a, b, c, inp[5], S12, 0x4787C62AL) # 6
c = XX(F, c, d, a, b, inp[6], S13, 0xA8304613L) # 7
b = XX(F, b, c, d, a, inp[7], S14, 0xFD469501L) # 8
a = XX(F, a, b, c, d, inp[8], S11, 0x698098D8L) # 9
d = XX(F, d, a, b, c, inp[9], S12, 0x8B44F7AFL) # 10
c = XX(F, c, d, a, b, inp[10], S13, 0xFFFF5BB1L) # 11
b = XX(F, b, c, d, a, inp[11], S14, 0x895CD7BEL) # 12
a = XX(F, a, b, c, d, inp[12], S11, 0x6B901122L) # 13
d = XX(F, d, a, b, c, inp[13], S12, 0xFD987193L) # 14
c = XX(F, c, d, a, b, inp[14], S13, 0xA679438EL) # 15
b = XX(F, b, c, d, a, inp[15], S14, 0x49B40821L) # 16
# Round 2.
S21, S22, S23, S24 = 5, 9, 14, 20
a = XX(G, a, b, c, d, inp[1], S21, 0xF61E2562L) # 17
d = XX(G, d, a, b, c, inp[6], S22, 0xC040B340L) # 18
c = XX(G, c, d, a, b, inp[11], S23, 0x265E5A51L) # 19
b = XX(G, b, c, d, a, inp[0], S24, 0xE9B6C7AAL) # 20
a = XX(G, a, b, c, d, inp[5], S21, 0xD62F105DL) # 21
d = XX(G, d, a, b, c, inp[10], S22, 0x02441453L) # 22
c = XX(G, c, d, a, b, inp[15], S23, 0xD8A1E681L) # 23
b = XX(G, b, c, d, a, inp[4], S24, 0xE7D3FBC8L) # 24
a = XX(G, a, b, c, d, inp[9], S21, 0x21E1CDE6L) # 25
d = XX(G, d, a, b, c, inp[14], S22, 0xC33707D6L) # 26
c = XX(G, c, d, a, b, inp[3], S23, 0xF4D50D87L) # 27
b = XX(G, b, c, d, a, inp[8], S24, 0x455A14EDL) # 28
a = XX(G, a, b, c, d, inp[13], S21, 0xA9E3E905L) # 29
d = XX(G, d, a, b, c, inp[2], S22, 0xFCEFA3F8L) # 30
c = XX(G, c, d, a, b, inp[7], S23, 0x676F02D9L) # 31
b = XX(G, b, c, d, a, inp[12], S24, 0x8D2A4C8AL) # 32
# Round 3.
S31, S32, S33, S34 = 4, 11, 16, 23
a = XX(H, a, b, c, d, inp[5], S31, 0xFFFA3942L) # 33
d = XX(H, d, a, b, c, inp[8], S32, 0x8771F681L) # 34
c = XX(H, c, d, a, b, inp[11], S33, 0x6D9D6122L) # 35
b = XX(H, b, c, d, a, inp[14], S34, 0xFDE5380CL) # 36
a = XX(H, a, b, c, d, inp[1], S31, 0xA4BEEA44L) # 37
d = XX(H, d, a, b, c, inp[4], S32, 0x4BDECFA9L) # 38
c = XX(H, c, d, a, b, inp[7], S33, 0xF6BB4B60L) # 39
b = XX(H, b, c, d, a, inp[10], S34, 0xBEBFBC70L) # 40
a = XX(H, a, b, c, d, inp[13], S31, 0x289B7EC6L) # 41
d = XX(H, d, a, b, c, inp[0], S32, 0xEAA127FAL) # 42
c = XX(H, c, d, a, b, inp[3], S33, 0xD4EF3085L) # 43
b = XX(H, b, c, d, a, inp[6], S34, 0x04881D05L) # 44
a = XX(H, a, b, c, d, inp[9], S31, 0xD9D4D039L) # 45
d = XX(H, d, a, b, c, inp[12], S32, 0xE6DB99E5L) # 46
c = XX(H, c, d, a, b, inp[15], S33, 0x1FA27CF8L) # 47
b = XX(H, b, c, d, a, inp[2], S34, 0xC4AC5665L) # 48
# Round 4.
S41, S42, S43, S44 = 6, 10, 15, 21
a = XX(I, a, b, c, d, inp[0], S41, 0xF4292244L) # 49
d = XX(I, d, a, b, c, inp[7], S42, 0x432AFF97L) # 50
c = XX(I, c, d, a, b, inp[14], S43, 0xAB9423A7L) # 51
b = XX(I, b, c, d, a, inp[5], S44, 0xFC93A039L) # 52
a = XX(I, a, b, c, d, inp[12], S41, 0x655B59C3L) # 53
d = XX(I, d, a, b, c, inp[3], S42, 0x8F0CCC92L) # 54
c = XX(I, c, d, a, b, inp[10], S43, 0xFFEFF47DL) # 55
b = XX(I, b, c, d, a, inp[1], S44, 0x85845DD1L) # 56
a = XX(I, a, b, c, d, inp[8], S41, 0x6FA87E4FL) # 57
d = XX(I, d, a, b, c, inp[15], S42, 0xFE2CE6E0L) # 58
c = XX(I, c, d, a, b, inp[6], S43, 0xA3014314L) # 59
b = XX(I, b, c, d, a, inp[13], S44, 0x4E0811A1L) # 60
a = XX(I, a, b, c, d, inp[4], S41, 0xF7537E82L) # 61
d = XX(I, d, a, b, c, inp[11], S42, 0xBD3AF235L) # 62
c = XX(I, c, d, a, b, inp[2], S43, 0x2AD7D2BBL) # 63
b = XX(I, b, c, d, a, inp[9], S44, 0xEB86D391L) # 64
self.A = (self.A + a) & 0xffffffffL
self.B = (self.B + b) & 0xffffffffL
self.C = (self.C + c) & 0xffffffffL
self.D = (self.D + d) & 0xffffffffL
# print 'A=%s\nB=%s\nC=%s\nD=%s\n' % (hex(self.A), hex(self.B), hex(self.C), hex(self.D))
#padding,即补位:首字节为\x80
def padding(self, n, sz = None):
if sz is None:
sz = n
pre = 64 - 8
sz = struct.pack("Q", sz * 8)
pad = '\x80'
n += 1
if n % 64 <= pre:
pad += '\x00' * (pre - n % 64)
pad += sz
else:
pad += '\x00' * (pre + 64 - n % 64)
pad += sz
return pad
#最后八个字节为数据长度
def pad(self, msg):
return msg + self.padding(len(msg))
def md5_iter(self, padding_msg):
assert len(padding_msg) % 64 == 0
for i in range(0, len(padding_msg), 64):
block = padding_msg[i:i + 64]
self.md5_compress(padding_msg[i:i + 64])
def digest(self):
return struct.pack('<IIII', self.A, self.B, self.C, self.D)
def hexdigest(self):
return binascii.hexlify(self.digest()).decode()
def my_md5(self, msg):
padding_msg = self.pad(msg)
self.md5_iter(padding_msg)
return self.hexdigest() # 通过md5值,逆向算出最后一轮的magic number
def compute_magic_number(self, md5str):
self.A = struct.unpack("I", md5str[0:8].decode('hex'))[0]
self.B = struct.unpack("I", md5str[8:16].decode('hex'))[0]
self.C = struct.unpack("I", md5str[16:24].decode('hex'))[0]
self.D = struct.unpack("I", md5str[24:32].decode('hex'))[0]
# print '\nA=%s\nB=%s\nC=%s\nD=%s\n' % (hex(self.A), hex(self.B), hex(self.C), hex(self.D))
def extension_attack(self, md5str, str_append, lenth):
self.compute_magic_number(md5str)
p = self.padding(lenth)
padding_msg = self.padding( len(str_append), lenth + len(p) + len(str_append) )
# url = repr(padding_msg.replace(' ', ''))
# print 'padding:%r' % (padding_msg)
self.md5_iter(str_append + padding_msg)
return self.hexdigest()
if __name__ == "__main__":
m = md5()
if 1:
lenth = int(raw_input(u'salt的hash长度:'))
md5 = raw_input(u'salt的hash值:')
value = raw_input(u'附加值:')
print u'md5(salt + padding + 附加值):' + m.extension_attack(md5, value, lenth)
#这个是padding的值,得看情况,实在不行就手动算。salt长度 + padding = 64
print ur'padding+附加值:' + r'%08' + r'%00' * (64 - 1 - lenth - 8) + r'%' + str(hex(lenth * 8)).replace(r'0x','') + r'%00' * 7 + value
else:
print m.my_md5("123456")
src = m.pad(sys.argv[1]) + sys.argv[2]
print 'md5 padding ->', m.my_md5(src)
还有一个python扩展hashpumpy,但是我折腾好一阵都安装不上,那个更好用,自己写也是无奈之举
根据源代码,username必须是admin,这样的话password必须是admin+padding+附加值
这样的话post上去的就是
username=admin&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%c8%00%00%00%00%00%00%00asd
在数据包里设置cookies:getmein=bf3b234ff0410ee4ec09b470e314d8c9
就成功绕过验证了
这里面直接给出的salt的MD5值其实是secret+adminadmin的MD5,所以salt的长度得是25,最后补位的时候要注意,实在不行自己手动构造padding。但是salt+padding+附加值的MD5没问题
# 0x03后记
中途也是踩了好多坑
首先是MD5的实现过程和漏洞原理,看了好一阵才明白,还好
之后就是脚本的实现,hashpumpy到最后都没装上,只能自己写。MD5的python实现是从网上找的,也花了不少时间改。
最后是payload的问题,忘了题目给的MD5是secret+adminadmin的md5,结果脚本跑的是secret本身,和它的不一致,有歧义。当时太晚了神志不太清醒还以为是脚本写错了,卡了好长时间,最后手动padding才知道不是自己的问题