ThinkPHP v6.0.0~6.0.1 任意文件操作漏洞分析

点击阅读

漏洞简介

影响版本:ThinkPHP 6.0.0 ~ThinkPHP 6.0.1

漏洞危害:任意文件操作,getshell

官方补丁:https://github.com/top-think/framework/commit/1bbe75019ce6c8e0101a6ef73706217e406439f2

漏洞分析

1. 搭建环境

安装漏洞版本的 ThinkPHP

测试使用 composer create-project topthink/think=6.0.0 tp6.0.0 命令安装的时候会自动安装 6.0.2 版本的 framework

image-20200205201723699

可以使用 composer create-project topthink/framework=6.0.0 tpframework 命令下载指定版本的 framework,再替换过去即可

下载若出现 SSL: Handshake timed out 错误的可以尝试换源并在 php.ini 中重新设置超时时间

composer config -g repo.packagist composer https://packagist.phpcomposer.com

default_socket_timeout = 360

2. 复现

开启 Session,在全局的中间件定义文件中删除 Session 初始化注释

1
2
3
4
5
6
7
8
9
10
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];

自定义一个漏洞方法

1
2
3
4
5
6
public function vuln()
{
$para = Request::get('para');
Session::set('testSession', $para);
return 1;
}

发送以下请求

1
2
3
4
5
6
7
8
9
GET /index/vuln?para=%3C?php%20phpinfo();?%3E HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=/../../../000000000000000000.php
Upgrade-Insecure-Requests: 1

即在 web 根目录生成 000000000000000000.php (如果有写入权限的话)

image-20200210233257716

3. 分析

看下官方 github 的 commit

image-20200210233456562

$id 使用 ctype_alnum 检测其是否全部为字母和(或)数字字符

下面我就 debug 跟一下调用看看是如何通过 session 写入文件的

在开启 Session 初始化的全局中间件之后,ThinkPHP 会调用反射执行类的实例化,加载 \think\middleware\SessionInit

image-20200211001948777

之后通过中间件调度管道,调用 SessionInit 的 handle 类方法进行 session初始化

image-20200211002520475

跟进 handle 函数,来到 /vendor/topthink/framework/src/think/middleware/SessionInit.php,首先获取的 $varSessionId值为空,下面跟进 $this->session->getName()

image-20200211020829775

来到 /vendor/topthink/framework/src/think/session/Store.php,Store 类构造函数会调用其 setId 方法,来到漏洞代码处,但此时为 Store 类的初始化阶段,并没有 id 参数传入

image-20200211001311794

接着通过 Store 类的 getName 方法获取 sessionName,之后会赋值给 $cookieName

image-20200211004432030

而 sessionName 的值在 Store 类中定义好了的,即 "PHPSESSID"

image-20200211004515137

返回到 SessionInit,所以此处的 $cookieName 的值为"PHPSESSID"

image-20200211004610180

接着往下开始获取 $sessionId ,关键一步出现了,由于 $varSessionId 的值为空,所以 if 判断之后,$sessionId 的值就是名称为 PHPSESSID 的 cookie 值,也是后来写入的文件名,接下来将 $sessionId 直接传入 setId 函数进行判断

image-20200211005240955

又来到漏洞代码处,就是在个时候在这里未对 $id 值做除长度之外的限制,从而可以直接写入任意文件

image-20200211022612881

在上面将 PHPSESSID 的值赋值给 id后一路跟进,再次来到 /vendor/topthink/framework/src/think/session/Store.php,终于开始保存 session 数据,获取 $sessionId,即 PHPSESSID 的值,这里测试使用的是 c465f41ee715df8c726dcc4742a6.php

image-20200211011538058

将 data 序列化之后通过 write 函数将序列化的数据保存在 c465f41ee715df8c726dcc4742a6.php 文件中

image-20200211012048948

跟进 write 函数,在文件名前加上了 sess_ 前缀,再调用 writeFile 函数写入

image-20200211012359526

跟进 writeFile,最终 file_put_contents 完成写入

image-20200211012532329

成功写入 WebShell

image-20200211013012377

在 /vendor/topthink/framework/src/think/session/Store.php:254 中不难看出还有个 delete 函数,用于删除 session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function save(): void
{
$this->clearFlashData();

$sessionId = $this->getId();

if (!empty($this->data)) {
$data = $this->serialize($this->data);

$this->handler->write($sessionId, $data);
} else {
$this->handler->delete($sessionId);
}

$this->init = false;
}

这里我也进行了测试,看能否删掉在 web 根目录下的 000000000000000000.php,跟进 delete,

image-20200211024527031

显然是通过 getFileName 获取文件名后直接进行删除操作了,但是 getFileName 函数会自动对文件名加上 sess_ 前缀

image-20200211024704834

这就直接导致在最后 unlink 删除操作之前的 is_file 判断过不了

image-20200211024827033

这也算是比较经典的问题了,因为 php 在读写文件时使用的是 php_stream_open_wrapper_ex 进行流处理,其最后会使用 tsrm_realpath 函数将文件名标准化成一个绝对路径, 通过处理 ../ 等特殊符号,文件路径中间有不存在的目录时也不会影响,而判断文件存在、重命名、删除文件等操作无需打开文件流,也就不会进行这种处理,导致报错

image-20200211025421722

漏洞总结

该漏洞主要危害还是文件写入,而文件删除实际测试来看还是比较有限制的,可操作性不强

v6 版本的 ThinkPHP 较之前版本还是有不少变化的,通过 debug 逐步分析漏洞能更好的捋清漏洞的形成过程,了解新的框架执行流程和开发思想

Reference

https://paper.seebug.org/1114/

http://d1iv3.me/2018/04/15/%E4%BB%8EPHP%E6%BA%90%E7%A0%81%E7%9C%8BPHP%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7/

文章作者: J0k3r
文章链接: http://j0k3r.top/2020/03/02/ThinkPHP_v6.0.0_ArbitraryFileWriting/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J0k3r's Blog
支付宝打赏
微信打赏