Hash Length Extension Attack (MD5)
- MD5加密原理
-
MD5算法会对明文进行分组,每组长度64bytes,不足64bytes的组进行padding补齐
padding规则:将明文先用\x80和若干个\x00补齐至长度与56模64同余,最后8个bytes再用原明文长度信息补齐(注意长度信息单位为bit)栗子:md5(admin)
- ‘admin’ equals '\x61\x64\x6d\x69\x6e’
- [padding_step1] : adding ‘\x80’+’\x00’*50
- [padding_step2] : adding ‘\x28’+’\x00’*7
- [after_padding] : '\x61\x64\x6d\x69\x6e\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x00\x00\x00\x00\x00\x00\x00’
- 前面有一个大小端的问题要注意,但不在这篇blog里细说
-
MD5算法有固定的初始值A,B,C,D,每组64bytes长的字符串均分为16段,组成M数组,再与A,B,C,D进行一系列数学运算,能得到新的A,B,C,D,再以新的A,B,C,D和下一个M数组进行运算,最后得到的A,B,C,D(记得进行小端转换)拼接而成就是16bytes长的md5串
-
知道了加密原理,那就来看看长度扩展攻击吧(攻什么???
- 前提条件:已知secret的长度和对应的md5
- 攻击目的:在未知secret的情况下,获取secret+poc的md5值
- 原理浅析:通过手动paadding,即利用poc的前半部分先将secret补成md5(secret)时padding补齐的亚子(长度为64bytes的整数倍),然后再将poc的后半部分构造为自己需要的字符串即可。这样的话已知的md5(小端转换后并四等分)就等价于我们poc后半段要进行md5的初始A,B,C,D,从而得到想要的md5值
直接挂自己写的md5长度扩展攻击脚本(手搓的一个好处是md5每个加密步骤都摸透了…)
import getopt,sys
import binascii
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)))
RL = lambda x,n:(((x<<n)|(x>>(32-n)))&(0xffffffff))
def FF(a, b, c, d, x, s, ac):
a = (a+F ((b), (c), (d)) + (x) + (ac)&0xffffffff)&0xffffffff
a = RL ((a), (s))&0xffffffff
a = (a+b)&0xffffffff
return a
def GG(a, b, c, d, x, s, ac):
a = (a+G ((b), (c), (d)) + (x) + (ac)&0xffffffff)&0xffffffff
a = RL ((a), (s))&0xffffffff
a = (a+b)&0xffffffff
return a
def HH(a, b, c, d, x, s, ac):
a = (a+H ((b), (c), (d)) + (x) + (ac)&0xffffffff)&0xffffffff
a = RL ((a), (s))&0xffffffff
a = (a+b)&0xffffffff
return a
def II(a, b, c, d, x, s, ac):
a = (a+I ((b), (c), (d)) + (x) + (ac)&0xffffffff)&0xffffffff
a = RL ((a), (s))&0xffffffff
a = (a+b)&0xffffffff
return a
def init_plain(plain, pre_len):
lens = ((len(plain)+pre_len)*8) & 0xffffffffffffffff
if(len(plain)%64 != 56):
adds_idx = (120 - len(plain)%64)%64 - 1
plain += '\x80' + '\x00' * adds_idx
lens = ((lens & 0xff00000000000000) >> 56) | ((lens & 0x00ff000000000000) >> 40) | ((lens & 0x0000ff0000000000) >> 24) | ((lens & 0x000000ff00000000) >> 8) | ((lens & 0x00000000ff000000) << 8) | ((lens & 0x0000000000ff0000) << 24) | ((lens & 0x000000000000ff00) << 40) | ((lens & 0x00000000000000ff) << 56)
tail_str = "%016x"%lens
for i in range(8):
plain += chr(int(tail_str[i*2 : (i+1)*2], 16))
return plain
def get_M(sub_plain, M):# 每次将64字节的sub_plain16等分
for i in range(16):
M[i] = ord(sub_plain[i*4]) + ord(sub_plain[i*4+1])*16**2 + ord(sub_plain[i*4+2])*16**4 + ord(sub_plain[i*4+3])*16**6
M[i] = M[i] & 0xffffffff
# print("%08x"%M[i])
def format_trans(a):#小端规则转换输出
a = ((a & 0xff000000) >> 24) | ((a & 0x00ff0000) >> 8) | ((a & 0x0000ff00) << 8) | ((a & 0x000000ff) << 24)
return a
def my_md5(plain, pre_len = 0, A = 0x67452301, B = 0xefcdab89, C = 0x98badcfe, D = 0x10325476):
a = A
b = B
c = C
d = D
plain = init_plain(plain, pre_len)
for i in range(0, len(plain) // 64):
sub_plain = plain[i*64 : (i+1)*64]
M = [0] * 16
get_M(sub_plain, M)
a = FF(a, b, c, d, M[0], 7 , -680876936);
d = FF(d, a, b, c, M[1], 12, -389564586);
c = FF(c, d, a, b, M[2], 17, 606105819);
b = FF(b, c, d, a, M[3], 22, -1044525330);
a = FF(a, b, c, d, M[4], 7 , -176418897);
d = FF(d, a, b, c, M[5], 12, 1200080426);
c = FF(c, d, a, b, M[6], 17, -1473231341);
b = FF(b, c, d, a, M[7], 22, -45705983);
a = FF(a, b, c, d, M[8], 7 , 1770035416);
d = FF(d, a, b, c, M[9], 12, -1958414417);
c = FF(c, d, a, b, M[10], 17, -42063);
b = FF(b, c, d, a, M[11], 22, -1990404162);
a = FF(a, b, c, d, M[12], 7 , 1804603682);
d = FF(d, a, b, c, M[13], 12, -40341101);
c = FF(c, d, a, b, M[14], 17, -1502002290);
b = FF(b, c, d, a, M[15], 22, 1236535329);
a = GG(a, b, c, d, M[1], 5 , -165796510);
d = GG(d, a, b, c, M[6], 9 , -1069501632);
c = GG(c, d, a, b, M[11], 14, 643717713);
b = GG(b, c, d, a, M[0], 20, -373897302);
a = GG(a, b, c, d, M[5], 5 , -701558691);
d = GG(d, a, b, c, M[10], 9 , 38016083);
c = GG(c, d, a, b, M[15], 14, -660478335);
b = GG(b, c, d, a, M[4], 20, -405537848);
a = GG(a, b, c, d, M[9], 5 , 568446438);
d = GG(d, a, b, c, M[14], 9 , -1019803690);
c = GG(c, d, a, b, M[3], 14, -187363961);
b = GG(b, c, d, a, M[8], 20, 1163531501);
a = GG(a, b, c, d, M[13], 5 , -1444681467);
d = GG(d, a, b, c, M[2], 9 , -51403784);
c = GG(c, d, a, b, M[7], 14, 1735328473);
b = GG(b, c, d, a, M[12], 20, -1926607734);
a = HH(a, b, c, d, M[5], 4 , -378558);
d = HH(d, a, b, c, M[8], 11, -2022574463);
c = HH(c, d, a, b, M[11], 16, 1839030562);
b = HH(b, c, d, a, M[14], 23, -35309556);
a = HH(a, b, c, d, M[1], 4 , -1530992060);
d = HH(d, a, b, c, M[4], 11, 1272893353);
c = HH(c, d, a, b, M[7], 16, -155497632);
b = HH(b, c, d, a, M[10], 23, -1094730640);
a = HH(a, b, c, d, M[13], 4 , 681279174);
d = HH(d, a, b, c, M[0], 11, -358537222);
c = HH(c, d, a, b, M[3], 16, -722521979);
b = HH(b, c, d, a, M[6], 23, 76029189);
a = HH(a, b, c, d, M[9], 4 , -640364487);
d = HH(d, a, b, c, M[12], 11, -421815835);
c = HH(c, d, a, b, M[15], 16, 530742520);
b = HH(b, c, d, a, M[2], 23, -995338651);
a = II(a, b, c, d, M[0], 6 , -198630844);
d = II(d, a, b, c, M[7], 10, 1126891415);
c = II(c, d, a, b, M[14], 15, -1416354905);
b = II(b, c, d, a, M[5], 21, -57434055);
a = II(a, b, c, d, M[12], 6 , 1700485571);
d = II(d, a, b, c, M[3], 10, -1894986606);
c = II(c, d, a, b, M[10], 15, -1051523);
b = II(b, c, d, a, M[1], 21, -2054922799);
a = II(a, b, c, d, M[8], 6 , 1873313359);
d = II(d, a, b, c, M[15], 10, -30611744);
c = II(c, d, a, b, M[6], 15, -1560198380);
b = II(b, c, d, a, M[13], 21, 1309151649);
a = II(a, b, c, d, M[4], 6 , -145523070);
d = II(d, a, b, c, M[11], 10, -1120210379);
c = II(c, d, a, b, M[2], 15, 718787259);
b = II(b, c, d, a, M[9], 21, -343485551);
A += a
B += b
C += c
D += d
A = A & 0xffffffff
B = B & 0xffffffff
C = C & 0xffffffff
D = D & 0xffffffff
a = A
b = B
c = C
d = D
return ("%08x%08x%08x%08x"%(format_trans(a),format_trans(b),format_trans(c),format_trans(d)))
try:
options,args = getopt.getopt(sys.argv[1:],"o:l:a:",["atk"])# o - orginal_md5 ; l - secret length ; a - addmsg
except getopt.GetoptError:
print('Required format : [-o "your original md5" -l "length of unknown secret" -a "your additional message" --atk]')
sys.exit()
o_md5 = ''
secret_len = 0
a_msg = ''
hasSoul = False #有没有灵魂(不是
for key,value in options:
if key == "-o":
o_md5 = value
elif key == "-a":
a_msg = value
elif key == "-l":
secret_len = int(value)
elif key == "--atk":
hasSoul = True
if o_md5 == '' or a_msg == '' or secret_len == 0 or hasSoul == False:
print('Required format : [-o "your original md5" -l "length of unknown secret" -a "your additional message" --atk]')
sys.exit()
payload = ''
lens = (secret_len*8) & 0xffffffffffffffff
if((secret_len%64) != 56):
adds_idx = (120 - secret_len%64)%64 - 1
payload += '\x80' + '\x00' * adds_idx
lens = ((lens & 0xff00000000000000) >> 56) | ((lens & 0x00ff000000000000) >> 40) | ((lens & 0x0000ff0000000000) >> 24) | ((lens & 0x000000ff00000000) >> 8) | ((lens & 0x00000000ff000000) << 8) | ((lens & 0x0000000000ff0000) << 24) | ((lens & 0x000000000000ff00) << 40) | ((lens & 0x00000000000000ff) << 56)
tail_str = "%016x"%lens
for i in range(8):
payload += chr(int(tail_str[i*2 : (i+1)*2], 16))
pre_len = secret_len + len(payload) #secret padding以后的长度pre_len
payload += a_msg
payload = ''.join([('%' + "%02x"%ord(c)) for c in payload])
print('[payload]: "' + '*'*secret_len + '"' + payload + ' ("' + '*'*secret_len + '" means the secret)')
MAGIC_NUM = [0] * 4 #从md5中获取自定义幻数
for i in range(4):
MAGIC_NUM[i] = format_trans(int(o_md5[i*8 : (i+1)*8], 16))
print('[new_md5]: ' + my_md5(a_msg, pre_len, MAGIC_NUM[0], MAGIC_NUM[1], MAGIC_NUM[2], MAGIC_NUM[3]))
-
代码实验:
format : [-o “your original md5” -l “length of unknown secret” -a “your additional message” --atk]
这里取secret = ‘imp0ssib1e’
但secret未知,我们假设仅知道secret对应的
[Length] : 10
[MD5] : 989c382ee68677c13e94b4c6e7ee5331运行结果如下:
- 实战利用
- CTF(略)
- 伪造合法签名(如AWS签名)
这里摘录一下白帽子讲web安全那本书
签名一般要加盐,所以相当于明文未知,在没有使用间隔符的签名校验算法里,我们就可以利用长度扩展攻击来生成合法签名,栗子如下
原来的参数:
?a=1&b=2&c=3
在签名算法中:
a1b2c3
伪造参数:
?a=1b2c3[…padding…]&b=4&c=5
在签名算法中:
a1b2c3[…padding…]b4c5
于是新的合法签名伪造成功!
写脚本给自己用系列(卑微