ThinkPHP6漏洞复现
1.漏洞描述
2020年1月10日,ThinkPHP团队发布一个补丁更新,修复了一处由不安全的SessionId导致的任意文件操作漏洞。该漏洞允许攻击者在目标环境启用session的条件下创建任意文件以及删除任意文件,在特定情况下还可以getshell。
2. 受影响版本
具体受影响版本为ThinkPHP6.0.0-6.0.1。
3. 漏洞利用
利用方式:PHPSESSID为32位字符可触发漏洞利用
利用示例:删除数据库配置文件
4. 复现环境
Windows:phpstudy + Thinkphp(6.0.0) + php7.2.1
先安装composer工具,composer(windows版本)下载路径https://getcomposer.org/Composer-Setup.exe,php版本使用7以上。
thinkphp6.0.0环境搭建运行composer create-project topthink/think tp 6.0.0
tp是项目文件夹名称 6.0.0是版本号cd tp && php think run
#会开启一个临时的开发环境的服务器,默认运行在localhost:8000
5. 漏洞缺陷代码位置
src/think/session/Store.php
6. 审计分析
6.1. 流程分析
前提条件:session功能已打开
6.2. 代码分析
第一步:通过官方的修复可以推出来漏洞的成因,主要原因应该是出现在Session中,那下面就来分析一下Session的相关代码.
/vendor/topthink/framework/src/think/session/Store.php #TP6存储Session文件的目录
漏洞介绍中说到该漏洞允许攻击者在目标环境启用session的条件下可以删除任意文件和创建任意文件,那就先定位一下关键词delete,既然是任意文件删除,那么参数就一定是用户可控的。
含delete关键字的一共有三处,可以推测在函数265行的位置很可能是该漏洞的形成位置,因为涉及到了SessionId。而且漏洞介绍中也说了可以任意创建文件,在delete关键字上面就有一个write函数且包含有SessionId。
跟踪一下这个write函数,找到write()和delete()函数的详细定义
第二步:这里只分析write函数,跟进write的getFileName和writeFile函数。
- getFilename函数功能是根据name构建了一个新的文件名,如果文件不存在则新建,这里可以看到只要name可控就可以控制最后的文件名
- WriteFile函数功能将content内容写入到path路径中
在返回最开始处梳理代码的整理逻辑,如果sessionId构造的文件中,data数据为空则执行删除操作
第三步:回到write函数调用处继续追踪session的赋值
/**
* session_id设置
* @access public
* @param string $id session_id
* @return void
*/
public function setId($id = null): void
{
//漏洞未修复前的代码
$this->id = is_string($id) && strlen($id) === 32 ? $id : md5(microtime(true) . session_create_id());
}
当传入的参数this->id,观察这个值是否可控,继续追踪setId被调用的地方直到setId,查看id赋值来源于$sessionId。
第四步::继续追踪查看下cookieName的值,发现$varSessionId为空,cookieName值为PHPSESSID。
再回到setid调用的地方,可以发现PHPSESSID来源于cookie,而cookie可控,结合最开始的分析得出结论只要控制了cookie中PHPSESSID值就会产生创建任意文件漏洞,如果sesion的值可控甚至会造成getshell。任意文件删除漏洞分析过程类似。
6.3. 漏洞复现
tp6默认未开启session,修改源代码主动打开session功能。
删除/app/middleware.php最后一行的注释即可
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];
在首页app\controller\index.php使用session
<?php
namespace app\controller;
use think\facade\Session;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
Session::set('tp','thinkphp6');
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V6<br/><span style="font-size:30px">13载初心不改 - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}
…
源代码修改好后抓包测试
执行后查看生成session文件,通过PHPSESSID可控制文件类型为php文件,如果反序列化内容包含一句话木马,就可以getshell。