PHP中MD5碰撞Bypass

CTF中有几个MD5绕过的技巧

这里总结一下,顺便记录一下MD5碰撞的一些工具和脚本(春节鸽了好久

MD5函数漏洞

$str1 = $_GET['str1'];
$str2 = $_GET['str2'];
if (md5($str1) == md5($str2)) {
    die('OK');
}

这个其实不是MD5函数的锅,而是php弱类型比较产生的漏洞

想要满足这个判断只需要构造出MD5值为0e开头的字符串,这样的话弱类型比较会认为是科学技术法,0的多少次方都是0,因此可以绕过

有一些字符串的MD5值为0e开头,这里记录一下

  • QNKCDZO
  • 240610708
  • s878926199a
  • s155964671a
  • s214587387a

还有MD5和双MD5以后的值都是0e开头的

  • CbDLytmyGm2xQyaLNhWn
  • 770hQgrBOjrcqftrlaZk
  • 7r4lGXCH2Ksu2JNT3BYM

脚本贴上

# -*- coding: utf-8 -*-
import multiprocessing
import hashlib
import random
import string
import sys
CHARS = string.letters + string.digits
def cmp_md5(substr, stop_event, str_len,. start=0, size=20):
    global CHARS
    while not stop_event.is_set():
        rnds = ''.join(random.choice(CHARS) for _ in range(size))
        md5 = hashlib.md5(rnds)
        value = md5.hexdigest()
        if value[start: start+str_len] == substr:
            print rnds
            stop_event.set()
            '''
            #碰撞双md5
            md5 = hashlib.md5(value)
            if md5.hexdigest()[start: start+str_len] == substr:
                print rnds+ "=>" + value+"=>"+ md5.hexdigest()  + "\n"
                stop_event.set()
            '''

if __name__ == '__main__':
    substr = sys.argv[1].strip()
    start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
    str_len = len(substr)
    cpus = multiprocessing.cpu_count()
    stop_event = multiprocessing.Event()
    processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
                                         stop_event, str_len, start_pos))
                 for i in range(cpus)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

PHP特性

比如这段

$str1 = $_GET['str1'];
$str2 = $_GET['str2'];

if (md5($str1) === md5($str2)) {
    die('OK');
}

因为是强类型比较,用0e开头的字符串是没办法绕过的了,但是PHP自身的特性使得可以提交一个数组

而md5函数传入数组的返回值都是NULL,这样就可以绕过强类型比较了

所以这里用GET传入?str1[]=1&str2[]=2就行了

MD5碰撞

比如这个程序

$str1 = (string)$_GET['str1'];
$str2 = (string)$_GET['str2'];

if (md5($str1) === md5($str2)) {
    die('OK');
}

由于强制类型转换,传数组就不可行了,这里就需要MD5碰撞

对于需要两个内容不同但是MD5值相同的文件,使用Fastcoll就可以了

macOS编译各种error,实在搞不来了,只好用win虚拟机跑了

./fastcoll -o file1 file2

fastcoll1

fastcoll2

可以看到MD5值完全相同,但是文件并不一样

但是当需要三个MD5值相同的文件怎么办

这里bdwms找到了个13年的ctf题的WP,是需要5个相同MD5值的可执行文件,这里照着做就可以得到5个不同但MD5值相同的文件了

原理没有看太懂,大致就是在base文件尾部附加内容使得MD5值相同

MD5(file + col1_a + col2_a) === MD5(file + col1_a + col2_b) === MD5(file + col1_b + col2_a) === MD5(file + col1_b + col2_b)

照着做,生成几个文件,然后用python脚本拼接一下

fastcoll3

base = open('a').read()

file_1 = open('b').read()
coll_1 = file_1[len(base):]
file_2 = open('c').read()
coll_2 = file_2[len(base):]

file_1_1 = open("e").read()
coll_1_1 = file_1_1[len(file_1):]
file_1_2 = open("f").read()
coll_1_2 = file_1_2[len(file_2):]

file_1_1_1 = open("g").read()
file_1_1_2 = open("h").read()
coll_1_1_1 = file_1_1_1[len(file_1_1):]
coll_1_1_2 = file_1_1_2[len(file_1_2):]


def w(fn, data):
    f = open("out/" + fn, "w")
    f.write(data)
    f.close()

w("file1", base + coll_1 + coll_1_1 + coll_1_1_1)
w("file2", base + coll_1 + coll_1_1 + coll_1_1_2)
w("file3", base + coll_1 + coll_1_2 + coll_1_1_1)
w("file4", base + coll_1 + coll_1_2 + coll_1_1_2)
w("file5", base + coll_2 + coll_1_1 + coll_1_1_1)

这里就不放出两两文件的比较结果了

fastcoll4

这样就可以了,注意一下要用url编码

import binascii

file1 = open('file1', 'rb').read()
file2 = open('file2', 'rb').read()
file3 = open('file3', 'rb').read()

def urlencode(bstr):
    encoded = ''
    for i in bstr:
        encoded = encoded + '%' + binascii.b2a_hex(i)
    return encoded

print urlencode(file1)
print urlencode(file2)
print urlencode(file3)

然后GET传进去就可以了

发表评论

发表评论

沙发空缺中,还不快抢~