当时i春秋的CTF有一题thinkphp6 任意文件写入getshell。现在来分析一下
漏洞概述
在 ThinkPHP 6.0.0 以及 6.0.1 两个版本中,如果服务端在全局中间件定义文件中开启了 Session 初始化,并且攻击者可控任意一个 session 的值,就可利用写入 session 文件的功能做到写入任意文件 getshell 。
安装Thinkphp6:
composer create-project topthink/think tp
修改配置:
修改app/middleware.php
为如下配置
并删除vendor/topthink/framework/src/think/session/Store.php
中setid的补丁
既然官方在这里打了补丁。那么就从这为入口点分析
补丁即对sessionid只能为数字字母。那么漏洞应该就是在sessionid上
先看setId被谁调用了
if ($sessionId) {
$this->session->setId($sessionId);
}
#只有满足$sessionId才能进入setid函数
if ($varSessionId && $request->request($varSessionId)) {
$sessionId = $request->request($varSessionId);
#如果在配置中。自定义了SESSID那就取自定义的
} else {
$sessionId = $request->cookie($cookieName);
#否则。就默认PHPSESSID
}
#这里sessionId可以由cookieName来。继续看
$cookieName = $this->session->getName();
可以看到$sessionId=$request->cookie('PHPSESSID')
sessionId其实就是等于PHPSESSID的值。现在$this->id
已经知道是PHPSESSID的值。
在setId函数下方有个getId函数
看看哪里调用了
在vendor/topthink/framework/src/think/session/Store.php
中发现save()函数
根据函数的注释。这个函数用来保存session数据
$sessionId为id名。然后session数据为$data
如果$data不为空。就序列化为字符串。调用write函数
我们继续看write函数。
在vendor/topthink/framework/src/think/session/driver/File.php
中发现write函数
$filename为sessionid名
$data为sessionid的值
最后会通过writeFile函数写入。
继续搜writeFile函数
在同文件的170行发现此函数。就是个file_put_contents
简单概括下。这个漏洞。就是讲sessionid作为文件名。session内容作为文件内容写入。
如果我们能控制sessionid和文件内容。即可任意文件写入
public function setId($id = null): void
{
$this->id = is_string($id) && strlen($id) === 32) ? $id : md5(microtime(true) . session_create_id());
}
在setid时有个判断。sessionid必须是长度为32的字符串。那么我们构造
PHPSESSID=1234567890123456789012345678.php
就可以写入一个名为sess_1234567890123456789012345678.php的文件。只需要一个文件内容可控即可
接下来具体分析下i春秋那题
首先下载源码。
正常来说。index.php应该是在public目录下作为程序入口。web目录也是public
可这个直接在根目录下。那么app。config。runtime这些目录只要有权限都可访问
结合thinkphp6任意文件。那么我们只要写入文件。可以直接访问runtime/session。就可拿到我们的webshell
OK。从程序入口文件入手
HTTP请求结束后,会调用end方法
接着会继续执行下去。又是一个end方法
进去看。末尾***还是end方法。
到这里调用了save方法
而save方法。就是我们上述分析的漏洞利用点
PHPSESSID我们可控。改为32长度以.php结尾的字符串即可
接下来找session内容可控处
在app/home/controller/Member.php中。我们找到了search方法
这里。我们搜索内容可控。并且会放在session内容中。
这里。我们在登陆时修改phpsession。为xxxxxxxxxxxxxxxxx.php(总长32)
登陆成功。会给10c26cc264e03957eb7bba1f6d75.php
这个PHPSESSID赋一个UID的值表示登陆
登陆后搜索<?php phpinfo();?>
此时PHPSESSID还是10c26cc264e03957eb7bba1f6d75.php
带有登陆标志。可以通过search的判断
访问/runtime/session/sess_10c26cc264e03957eb7bba1f6d75.php
可以看到phpinfo的界面