Hash长度扩展攻击

Hash长度扩展攻击,是一个比较有意思的攻击方式,服务端给一段明文加盐后hash,可以利用已知HASH值来预测特定字符串的Hash值。

Hash长度扩展攻击

引子

无意中碰到一道题,大概代码是这样的

$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
}
}

这个核心的判断在第二个if的判断

if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password)))

也就是需要构造getmein的cookie和他那串字符相同就可以,但是问题是这个$secret的变量我们根本不知道啊

网上找了些关于MD5的资料,发现MD5是存在Hash长度扩展攻击的

MD5算法

MD5的算法具体比较复杂,不是一时半会可以看透的,但是从表面的一个加密过程来看就是类似下图20140606204046187

首先是对加密的字符串进行填充,使之(二进制)补到448模512同余,即长度为512的倍数减64,最后的64位在补充为原来字符串的长度,这样刚好补满512位的倍数,之后再将这512位分组,进行复杂的运算,具体过程不阐述。需要知道的就是进行MD5运算必须要是512的整数倍,不是整数倍会将其补全,还有一个重点是他的计算过程是512位进行一次运算,也就是如果消息长度小于512就只需要计算一次,大于512小于1024是计算两次,以此类推。

在MD5中有四个32位的被称作链接变量的整数参数,是如下设置(这个ABCD是初始的固定的值):

A=0x67452301,

B=0xefcdab89,

C=0x98badcfe,

D=0x10325476。

之后有四个非线性函数,将字符串和那四个链接变量经过一系列的复杂运算,算出一组新的A,B,C,D的值,如果消息小于512,也就是只需要计算一次,这时候将新的ABCD的值按ABCD的顺序级联,然后输出,就是MD5的值,如果消息大于512的话,就用第一次算的MD5的值进行后半部分的运算,以此类推。

举个例子

比如计算字符串“test”

十六进制0x74657374

二进制0b1110100011001010111001101110100

这里与448模512不同余,补位后的数据如下

十六进制

0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000

二进制

0b1110100011001010111001101110100100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000

将补位后的数据进行一次复杂的运算,计算出

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726

数据小于512位,所以将ABCD通过小端规则转换就是MD5值:098f6bcd4621d373cade4e832627b4f6

如果我输入的数据不是test而是一串很长的字符,换算出来大于512小于1024,就需要计算两次,第一次先计算前512位的ABCD的值,算出来后再用这个ABCD去计算后面512位的的ABCD的值,最后算出来的ABCD经过拼接就是这串字符的MD5了

问题来了

如果这么一个情况,由两个字符串组成一个字符串(\$str=\$a+\$b),第一个字符串(\$a)我不知道也不可控,只可控第二个字符串(\$b),同时知道第一个字符串(\$a)的MD5值和长度,这时候我将第二个字符串精心构造一下,便可以算出合成的字符串\$str的MD5的值

首先正向计算一遍

假如第一个字符串\$a=“test”,为了方便转为十六进制0x74657374

我构造第二个字符串首先手动将$str补成一个标准的可以直接计算的512位

\$str=0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000

这样子,这时候再在后面追加一个0x746573748

\$str=0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000746573748

这时候再将$str大于512位,程序会先将这串数据补为1024位,补充完如下

\$str=0x7465737480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000074657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000

这时将$str分为两部分

74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000

74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000

这时候程序计算前一部分的ABCD的值,由于和之前算的test的数值是相同的所以

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726

到了第二部分,第二部分的计算是用的第一部分的ABCD去计算,计算新的ABCD如下

A=0x226359e5

b=0x99df12eb

C=0x6853f59e

D=0xf5406385

最后算出来的MD5是e5596322eb12df999ef55368856340f5

这时候我们按照给定条件来计算一遍

我们知道的条件

1.$a的MD5(098f6bcd4621d373cade4e832627b4f6)

2.$a的长度=4

3.$b我们可以任意控制

由1我们可以逆推算出其ABCD的值

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726

我们构造$b=’\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\x00\x20\x00\x00\x00\x00\x00\x00\x00’+’test’

此时$str如下,由于不知道\$a,我们假设\$a=”aaaa”

$str=’aaaa’+’\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\x00\x20\x00\x00\x00\x00\x00\x00\x00’+’test’

好了我们脑补一下程序计算str的过程

1.由于大于512位,先补全为1024位,

2.将其分为两部分

3.计算第一部分的ABCD的值

4.再用第一部分算出来的ABCD拿来算第二部分的值。

这里由于第一部分的ABCD我们可以逆推出来,我们可以直接跳过前三部分直接进行第四部分的计算,只需要将标准的MD5的源码里面的初始的ABCD的值改为逆推出来的那个值

我们用假的初始的ABCD计算一下

0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000

的MD5,发现是e5596322eb12df999ef55368856340f5,和上面正向计算出来的一样!

下面贴出来我用的算MD5的代码,以及测试文件

my_md5.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author:DshtAnger
# theory reference:
# blog:
# http://blog.csdn.net/adidala/article/details/28677393
# http://blog.csdn.net/forgotaboutgirl/article/details/7258109
# http://blog.sina.com.cn/s/blog_6fe0eb1901014cpl.html
# RFC1321:
# https://www.rfc-editor.org/rfc/pdfrfc/rfc1321.txt.pdf
##############################################################################
import sys
def genMsgLengthDescriptor(msg_bitsLenth):
'''
---args:
msg_bitsLenth : the bits length of raw message
--return:
16 hex-encoded string , i.e.64bits,8bytes which used to describe the bits length of raw message added after padding
'''
return __import__("struct").pack(">Q",msg_bitsLenth).encode("hex")
def reverse_hex_8bytes(hex_str):
'''
--args:
hex_str: a hex-encoded string with length 16 , i.e.8bytes
--return:
transform raw message descriptor to little-endian
'''
hex_str = "%016x"%int(hex_str,16)
assert len(hex_str)==16
return __import__("struct").pack("<Q",int(hex_str,16)).encode("hex")
def reverse_hex_4bytes(hex_str):
'''
--args:
hex_str: a hex-encoded string with length 8 , i.e.4bytes
--return:
transform 4 bytes message block to little-endian
'''
hex_str = "%08x"%int(hex_str,16)
assert len(hex_str)==8
return __import__("struct").pack("<L",int(hex_str,16)).encode("hex")
def deal_rawInputMsg(input_msg):
'''
--args:
input_msg : inputed a ascii-encoded string
--return:
a hex-encoded string which can be inputed to mathematical transformation function.
'''
ascii_list = [x.encode("hex") for x in input_msg]
length_msg_bytes = len(ascii_list)
length_msg_bits = len(ascii_list)*8
#padding
ascii_list.append('80')
while (len(ascii_list)*8+64)%512 != 0:
ascii_list.append('00')
#add Descriptor
ascii_list.append(reverse_hex_8bytes(genMsgLengthDescriptor(length_msg_bits)))
return "".join(ascii_list)
def getM16(hex_str,operatingBlockNum):
'''
--args:
hex_str : a hex-encoded string with length in integral multiple of 512bits
operatingBlockNum : message block number which is being operated , greater than 1
--return:
M : result of splited 64bytes into 4*16 message blocks with little-endian
'''
M = [int(reverse_hex_4bytes(hex_str[i:(i+8)]),16) for i in xrange(128*(operatingBlockNum-1),128*operatingBlockNum,8)]
return M
#定义函数,用来产生常数T[i],常数有可能超过32位,同样需要&0xffffffff操作。注意返回的是十进制的数
def T(i):
result = (int(4294967296*abs(__import__("math").sin(i))))&0xffffffff
return result
#定义每轮中用到的函数
#RL为循环左移,注意左移之后可能会超过32位,所以要和0xffffffff做与运算,确保结果为32位
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 = L = 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 show_md5(A,B,C,D):
return "".join( [ "".join(__import__("re").findall(r"..","%08x"%i)[::-1]) for i in (A,B,C,D) ] )
def run_md5(A=0x67452301,B=0xefcdab89,C=0x98badcfe,D=0x10325476,readyMsg=""):
a = A
b = B
c = C
d = D
for i in xrange(0,len(readyMsg)/128):
M = getM16(readyMsg,i+1)
for i in xrange(16):
exec "M"+str(i)+"=M["+str(i)+"]"
#First round
a=FF(a,b,c,d,M0,7,0xd76aa478L)
d=FF(d,a,b,c,M1,12,0xe8c7b756L)
c=FF(c,d,a,b,M2,17,0x242070dbL)
b=FF(b,c,d,a,M3,22,0xc1bdceeeL)
a=FF(a,b,c,d,M4,7,0xf57c0fafL)
d=FF(d,a,b,c,M5,12,0x4787c62aL)
c=FF(c,d,a,b,M6,17,0xa8304613L)
b=FF(b,c,d,a,M7,22,0xfd469501L)
a=FF(a,b,c,d,M8,7,0x698098d8L)
d=FF(d,a,b,c,M9,12,0x8b44f7afL)
c=FF(c,d,a,b,M10,17,0xffff5bb1L)
b=FF(b,c,d,a,M11,22,0x895cd7beL)
a=FF(a,b,c,d,M12,7,0x6b901122L)
d=FF(d,a,b,c,M13,12,0xfd987193L)
c=FF(c,d,a,b,M14,17,0xa679438eL)
b=FF(b,c,d,a,M15,22,0x49b40821L)
#Second round
a=GG(a,b,c,d,M1,5,0xf61e2562L)
d=GG(d,a,b,c,M6,9,0xc040b340L)
c=GG(c,d,a,b,M11,14,0x265e5a51L)
b=GG(b,c,d,a,M0,20,0xe9b6c7aaL)
a=GG(a,b,c,d,M5,5,0xd62f105dL)
d=GG(d,a,b,c,M10,9,0x02441453L)
c=GG(c,d,a,b,M15,14,0xd8a1e681L)
b=GG(b,c,d,a,M4,20,0xe7d3fbc8L)
a=GG(a,b,c,d,M9,5,0x21e1cde6L)
d=GG(d,a,b,c,M14,9,0xc33707d6L)
c=GG(c,d,a,b,M3,14,0xf4d50d87L)
b=GG(b,c,d,a,M8,20,0x455a14edL)
a=GG(a,b,c,d,M13,5,0xa9e3e905L)
d=GG(d,a,b,c,M2,9,0xfcefa3f8L)
c=GG(c,d,a,b,M7,14,0x676f02d9L)
b=GG(b,c,d,a,M12,20,0x8d2a4c8aL)
#Third round
a=HH(a,b,c,d,M5,4,0xfffa3942L)
d=HH(d,a,b,c,M8,11,0x8771f681L)
c=HH(c,d,a,b,M11,16,0x6d9d6122L)
b=HH(b,c,d,a,M14,23,0xfde5380c)
a=HH(a,b,c,d,M1,4,0xa4beea44L)
d=HH(d,a,b,c,M4,11,0x4bdecfa9L)
c=HH(c,d,a,b,M7,16,0xf6bb4b60L)
b=HH(b,c,d,a,M10,23,0xbebfbc70L)
a=HH(a,b,c,d,M13,4,0x289b7ec6L)
d=HH(d,a,b,c,M0,11,0xeaa127faL)
c=HH(c,d,a,b,M3,16,0xd4ef3085L)
b=HH(b,c,d,a,M6,23,0x04881d05L)
a=HH(a,b,c,d,M9,4,0xd9d4d039L)
d=HH(d,a,b,c,M12,11,0xe6db99e5L)
c=HH(c,d,a,b,M15,16,0x1fa27cf8L)
b=HH(b,c,d,a,M2,23,0xc4ac5665L)
#Fourth round
a=II(a,b,c,d,M0,6,0xf4292244L)
d=II(d,a,b,c,M7,10,0x432aff97L)
c=II(c,d,a,b,M14,15,0xab9423a7L)
b=II(b,c,d,a,M5,21,0xfc93a039L)
a=II(a,b,c,d,M12,6,0x655b59c3L)
d=II(d,a,b,c,M3,10,0x8f0ccc92L)
c=II(c,d,a,b,M10,15,0xffeff47dL)
b=II(b,c,d,a,M1,21,0x85845dd1L)
a=II(a,b,c,d,M8,6,0x6fa87e4fL)
d=II(d,a,b,c,M15,10,0xfe2ce6e0L)
c=II(c,d,a,b,M6,15,0xa3014314L)
b=II(b,c,d,a,M13,21,0x4e0811a1L)
a=II(a,b,c,d,M4,6,0xf7537e82L)
d=II(d,a,b,c,M11,10,0xbd3af235L)
c=II(c,d,a,b,M2,15,0x2ad7d2bbL)
b=II(b,c,d,a,M9,21,0xeb86d391L)
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
print "%x,%x,%x,%x"%(a,b,c,d)
return show_md5(a,b,c,d)

test.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import my_md5
samplehash="571580b26c65f306376d4f64e53cb5c7"
s1=0xcd6b8f09
s2=0x73d32146
s3=0x834edeca
s4=0xf6b42726
secret = "a"*4
test=secret+'\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\x00\x20\x00\x00\x00\x00\x00\x00\x00'+'test'
s = my_md5.deal_rawInputMsg(test)
r = my_md5.deal_rawInputMsg(secret_admin)
inp = s[len(r)/2:]
print test+'\n'
print '----------------------------------------------------------'
print s
print '----------------------------------------------------------'
print inp
print '----------------------------------------------------------'
print "md5:"+my_md5.run_md5(s1,s2,s3,s4,inp)
文章目录
  1. 1. Hash长度扩展攻击
    1. 1.0.0.1. 引子
    2. 1.0.0.2. MD5算法
      1. 1.0.0.2.1. 举个例子
    3. 1.0.0.3. 问题来了
      1. 1.0.0.3.1. 首先正向计算一遍
      2. 1.0.0.3.2. 这时候我们按照给定条件来计算一遍
|