[SWPUCTF 2022 新生赛]奇妙的MD5

piter 发布于 9 小时前 38 次阅读 1451 字


[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)) {
    // 过关
}

要同时:

  1. $x 与 $y 不相等(弱的「不等于」)
  2. 两边的 md5 结果在弱比较下相等

解法一:数组绕过(推荐先掌握)

?x[]=1&y[]=2

发生了什么:

  1. URL 里 x[]=1 让 PHP 把 $_GET['x'] 解析成数组,不是字符串
  2. 对数组调用 md5() 时,PHP 无法按字符串哈希,函数失败并返回 NULL(题里关掉报错所以你看不见 Warning)
  3. 两个都是 NULL → 弱比较下「相等」
  4. 两个数组内容不同(一个是 [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}

  • reward_image1
永远不要因为需要大量时间才能完成,就放弃梦想,时间怎么样都会过去的
最后更新于 2026-10-22