[SWPUCTF 2022 新生赛]奇妙的MD5
平台:NSSCTF 2638 · 题目页
这题三关,一路考 PHP 和 MD5 的怪脾气。和 funny_web 的弱比较类似,这里多了 raw MD5、SQL 拼接、数组绕过。
MD5
MD5 是一种哈希(摘要)算法:任意长度输入 → 固定长度输出。
- 同一输入,永远同一输出(可复现)
- 输出看起来像乱码,很难从输出反推输入(单向)
- 不同输入「理论上」得到不同输出;但 MD5 已不够安全,CTF 里常考的是 PHP 怎么用它,不是去破解 MD5 本身
在终端里:
echo -n ffifdyop | md5sum
看到 32 个十六进制字符。那是把 16 个字节用「每个字节写成两位 0-9a-f」展开后的样子。
PHP 里两种返回形式(本题第一关的关键)
md5($pass); // 默认:32 位十六进制字符串
md5($pass, true); // 第二参数 true:直接返回 16 字节原始二进制
| 写法 | 长度 | 长什么样 | 常见用途 |
|---|---|---|---|
| 默认 | 32 个字符 | 276f7227... 这种 |
存库、和你在网页上复制的 MD5 一致 |
| 第二参数 true | 16 字节 | 可能含引号、不可见字符 | 本题拼进 SQL 时的坑 |
十六进制只是「方便阅读的写法」;true 那种才是磁盘/网络里真正的 16 字节。
弱比较 vs 强比较
PHP 有两套「相等」:
- 弱比较(两个等号 / 两个不等号):先试着把两边转成同一类型,再比。字符串和数字可能搅在一起。
- 强比较(三个等号 / 三个不等号):类型和值都要一样,不搅类型。
对照 C 的直觉:弱比较有点像隐式类型转换;强比较更接近「类型也对上才算」。
弱比较为什么危险(CTF 常考)
例子 1:数字和数字串
var_dump(123 == "123"); // true,字符串被当成数字
var_dump(123 === "123"); // false,一个是 int,一个是 string
例子 2:科学计数法魔法(0e 串)
若一个字符串以 0e 开头、后面全是数字,弱比较时 PHP 可能把它当成「0 乘 10 的多少次方」,也就是数值 0:
var_dump("0e1234" == "0e5678"); // true,两边都被当成 0
var_dump("0e1234" === "0e5678"); // false,两个不同字符串
所以:两个看起来完全不同的字符串,弱比较可能「相等」。
MD5 的 32 位十六进制若碰巧长成 0e + 纯数字,就会中招——这就是第二关备选解法。
例子 3:和 funny_web 的关系
funny_web 里是 intval 截断 + 弱比较:字符串尾巴被丢掉后,数值部分还能对上。
本题第二、三关是:md5(...) 的结果拿去弱/强比较,或对非字符串参数(数组)会返回 NULL。
第一关:响应头 Hint + ffifdyop
怎么找线索
F12 → Network → 点页面请求(如 sta4t.php)→ 响应标头,找:
Hint: select * from 'admin' where password=md5($pass,true)
头里通常不直接写 ffifdyop,只给逻辑;字符串是根据 Hint 去搜或推的。
为什么偏偏是 ffifdyop
先算十六进制(方便人看):
276f722736c95d99e921722cf9ed621c
按字节切开前 4 字节:
| 十六进制 | 字符 |
|---|---|
27 |
单引号 ' |
6f |
o |
72 |
r |
27 |
单引号 ' |
也就是 raw(md5(..., true))开头是:'or'
后端类似这样拼 SQL:
$sql = "select * from 'admin' where password='".md5($pass, true)."'";
拼完变成:
where password=''or'6……'
读成人话:密码等于空 或者 后面那坨。or 一出,条件容易恒真 → 登录成功。
注意:必须是 md5($pass, true)。若只用默认的 32 位十六进制,结果是 276f7227... 这种可见字符,不会在 SQL 里变成「先闭合引号再 or」。
密码框填 ffifdyop 进下一关。


第二关:c0nt1nue.php(弱比较)
右键「查看网页源代码」,注释里大致是:
$x = $_GET['x'];
$y = $_GET['y'];
if ($x != $y && md5($x) == md5($y)) {
// 过关
}
要同时:
$x与$y不相等(弱的「不等于」)- 两边的
md5结果在弱比较下相等
解法一:数组绕过(推荐先掌握)
?x[]=1&y[]=2
发生了什么:
- URL 里
x[]=1让 PHP 把$_GET['x']解析成数组,不是字符串 - 对数组调用
md5()时,PHP 无法按字符串哈希,函数失败并返回NULL(题里关掉报错所以你看不见 Warning) - 两个都是
NULL→ 弱比较下「相等」 - 两个数组内容不同(一个是
[1],一个是[2])→ 「不等于」也成立
所以一发 GET 就过。这招不依赖撞库串,第三关强比较照样能用。
解法二:0e 撞库串(只吃弱比较)
找两个不同字符串,使它们的 md5 十六进制都以 0e 开头且后面全是数字,例如经典一对:
?x=QNKCDZO&y=240610708
两边 md5 都被弱比较当成数值 0,于是「哈希相等」;原串又不同。
换成强比较后这招失效(两个不同字符串,强比较不相等)。
第三关:f1na11y.php(强比较)
<?php
error_reporting(0);
include "flag.php";
highlight_file(__FILE__);
if ($_POST['wqh'] !== $_POST['dsy'] && md5($_POST['wqh']) === md5($_POST['dsy'])) {
echo $FLAG;
}
变化:
- 参数改成 POST:
wqh、dsy - 「不等于 / 相等」都改成强比较
- 页面在
f1na11y.php(文件名里是数字 1),不要还在c0nt1nue.php上发 POST
0e 失效;数组仍有效:两边 md5 都是 NULL,强比较下 NULL 与 NULL 也相等。
HackBar:URL 换成 f1na11y.php,打开 POST,Body:
wqh[]=1&dsy[]=2

| 关 | 页面 | 比较方式 | 做法 |
|---|---|---|---|
| 1 | 登录 | SQL + raw md5 | 响应头看 Hint → ffifdyop |
| 2 | c0nt1nue.php |
GET + 弱比较 | ?x[]=1&y[]=2(或 0e 双串) |
| 3 | f1na11y.php |
POST + 强比较 | wqh[]=1&dsy[]=2 |
Flag
NSSCTF{9ba19792-d249-40fa-a97f-e900404f8558}


Comments NOTHING