MD5的Hash長度擴展攻擊


Hash長度擴展攻擊

引子

大家可以去我的新博客地址:http://blog.leej.me
大家可以去我的新博客地址:http://blog.leej.me
大家可以去我的新博客地址:http://blog.leej.me
無意中碰到一道題,大概代碼是這樣的

$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的算法比較簡單,大概加密過程來看就是類似下圖!

首先是數據填充:首先要知道的是,md5后面運算過程都是需要512比特為一組來進行運算,先說一下簡單的數據比較少 不存在分組的時候的填充,首先512比特的末尾64比特是存放原明文消息的長度,512比特開始是明文數據緊接着明文后填一位1(2進制),其余全是0,假設我明文就一個字符串‘test’那么填充就是0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000
最后四字節也就是2000000000000000代表前面'test'的長度

下面說一下具體的一些計算:在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

# -*- coding: utf-8 -*-
import my_md5
import sys
import six
MD5_Hash=sys.argv[1]
length=int(sys.argv[2])
text=sys.argv[3]

s1=eval('0x'+MD5_Hash[:8].decode('hex')[::-1].encode('hex'))
s2=eval('0x'+MD5_Hash[8:16].decode('hex')[::-1].encode('hex'))
s3=eval('0x'+MD5_Hash[16:24].decode('hex')[::-1].encode('hex'))
s4=eval('0x'+MD5_Hash[24:32].decode('hex')[::-1].encode('hex'))

secret = "a"*length
test=secret+'\x80'+'\x00'*((512-length*8-8-8*8)/8)+six.int2byte(length*8)+'\x00\x00\x00\x00\x00\x00\x00'+text
s = my_md5.deal_rawInputMsg(test)
r = my_md5.deal_rawInputMsg(secret)
inp = s[len(r):]
print '填充完的數據為:'+test+'\n'
print '----------------------------------------------------------'
print '擴充完的數據為(16進制):'+s
print '----------------------------------------------------------'
print '截取最后分組的數據(16進制):'+inp
print '----------------------------------------------------------'

print  '最終填充結果為:'+bytes(test).encode('hex')
print "填充后的md5為:"+my_md5.run_md5(s1,s2,s3,s4,inp)


腳本使用時第一個命令行參數是一個服務端加密一個固定長度數據的md5,第二個參數是固定的長度
例如已知服務端加密一個15字符的md5:test.py 571580b26c65f306376d4f64e53cb5c7 15


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM