弄了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,之后的都是0x00padding的最后8个字节用来表示需要哈希的消息长度

defpadding(self,n,sz=None):

ifszisNone:

sz=n

pre=648

sz=struct.pack(“Q”,sz*8)

pad=\x80

n+=1

ifn%64<=pre:

pad+=\x00*(pren%64)

pad+=sz

else:

pad+=\x00*(pre+64n%64)

pad+=sz

returnpad

#最后八个字节为数据长度

defpad(self,msg):

returnmsg+self.padding(len(msg))

之后开始对每组消息进行压缩,经过64轮的数学变换(位运算的与或非异或、按位左移右移、按位无符号左移右移、进制转换、sin、和0xFF0xFFFFFFF做与运算保证位数不变等等)每一次变换压缩的结果被当成下一轮变换压缩的初始值。最开始的初始值是由文档规定好的。最后的结果拼在一起就是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才知道不是自己的问题