影响版本

ThinkPHP 6.0.0-6.0.1,这里使用 thinkphp 6.0.0 做漏洞复现、分析

环境搭建

php 集成环境使用 phpstudy,php 版本为 7.3.4 nts
thinkphp 6.x 使用 composer 进行安装,下载 https://getcomposer.org/Composer-Setup.exe 进行安装即可
安装完成后,打开 cmd,输入命令composer --version
image.png
设置 composer 镜像

  1. composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

进入 phpstudy 的 www 目录下,运行命令,其中 thinkphp6.0.0 是目录名称,后面的 6.0.0 是版本号

  1. composer create-project topthink/think thinkphp6.0.0 6.0.0

在 thinkphp 目录下使用php think version命令来查看当前 thinkphp 版本,当前 thinkphp 版本不是 6.0.0,需要调整 thinkphp 版本号
image.png
切换 thinkphp 版本到 6.0.0 composer require topthink/framework:6.0.0,然后再次查看 thinkphp 版本号
image.png

漏洞复现

thinkphp6 默认不开启 session,需要手动进行开启,代码位置在 app\middleware.php,取消掉 session 初始化下面代码的注释即可
image.png

任意文件写入

来到 app\controller\Index.php#index(),增加如下代码,目的就是为了增加一个可控的 session

  1. session('test', $_GET['f']);

image.png

默认写入 runtime/session 目录下

使用 burp 发送如下数据包,这里需要注意两个点

  1. get 请求中的空格需要使用 url 编码即 20% 替换
  2. http 请求头中的 PHPSESSID 的值为 32 位,除去后缀 .php后前面的 aaaa 是 28 位,加起来总共是 32 位

image.png
然后可以看到 runtime/session 下会生成一个 php 文件,sess_ 不计算长度,后面的 aaa.php 长度为 32位
image.png
访问该链接 http://127.0.0.1/thinkphp6.0.0/runtime/session/sess_aaaaaaaaaaaaaaaaaaaaaaaaaaaa.php
image.png

写入 public 目录下

或者可以这样子,直接写入 public 目录下
image.png
访问连接:http://127.0.0.1/thinkphp6.0.0/public/aaaaaaaaaaa.php
image.png

任意文件删除

在 app\controller\Index.php#index 中增加这样一个代码,同样,需要增加一个可控的 session

  1. session(null, $_GET['d']);

image.png
在 burp 下创建 bbbbbbbbbbb.txt 文件,使用 burp 尝试进行删除
image.png
image.png
成功删除文件
image.png
需要注意的是,这里的 PHPSESSID 的值也得是 32位

漏洞分析

thinkphp 官方 修正 sessionid 检查的一处隐患 的 commit 连接
该漏洞是存储 session 时进行了写入文件操作,从而导致的任意文件写入

文件写入/删除操作

vendor\topthink\framework\src\think\session\Store.php#save 函数,在 data 不为空后会进入 write 操作,在 data 为空后会进入 delete 操作,这里追踪 write 函数,先看任意文件写入漏洞
image.png
跟进 write 函数,在 vendor\topthink\framework\src\think\session\driver\File.php#write
image.png
继续跟进 writeFile 函数,最后在 vendor\topthink\framework\src\think\session\driver\File.php#writeFile 函数进行文件写入操作,file_put_contents 函数在 php 中用于进行文件写入操作
image.png
image.png
delete 函数同理,会调用 vendor\topthink\framework\src\think\session\driver\File.php 的 delete 函数和 unlink 函数进行删除文件操作
image.png

写入文件名可控

通过分析可以知道,sessionId 其实就是最终写入/删除的文件名,那么 sessionId 是怎么来的呢?
首先在 vendor\topthink\framework\src\think\session\Store.php#save 中调用了 getId 函数
image.png
查看 getId 函数,在 vendor\topthink\framework\src\think\session\Store.php#getId
image.png
这里的 id 又是在上面的 setId 函数中赋值的
image.png
在 vendor\topthink\framework\src\think\middleware\SessionInit.php#handle 处调用了 setId 方法
这里的 varSessionId 变量会从 config\session.php 文件中读取,默认为空,因此 if 判断会走到下面的 else 中
sessionId 的值是 cookie 中 PHPSESSID 的值,为攻击者可控的,从而导致写入文件名可控
image.png

写入的内容

写入的内容其实就是 vendor\topthink\framework\src\think\session\Store.php#save 中的 data 变量,先来看看 data 变量是如何获取的
image.png
data 变量预先定于在了 vendor\topthink\framework\src\think\session\Store.php 中并且默认为空
image.png
因此,data 内容关键在于对 session 的操作方法,session 不为空的话,会进行 session 文件写入操作,导致任意文件写入漏洞;session 为空的话,会造成任意文件删除漏洞,在复现过程中,我们对 session 的操作代码如下
image.png

参考