ThinkPHP 6.0.1 任意文件写入漏洞
漏洞简介
原理:thinkphp session写入文件功能的参数可控,并且过滤不严格导致了任意文件写入漏洞
影响范围:
6.0.0 <= thinkphp < 6.0.1
环境配置
- 搭建thinkphp6.0.1版本
- 修改配置文件开启session
app\middleware.php
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];
- 添加漏洞入口
app/controller/Index.php
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
$a = isset($_GET['a']) && !empty($_GET['a']) ? $_GET['a'] : '';
$b = isset($_GET['b']) && !empty($_GET['b']) ? $_GET['b'] : '';
session($a,$b);
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>';
}
public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}
代码审计
漏洞点
vendor/topthink/framework/src/think/session/driver/File.php

这里是调用链最后写文件的代码,我们接下来分析他的参数是否可控
$path参数
查找writeFile()的用用法,发现只有一个地方调用了
vendor/topthink/framework/src/think/session/driver/File.php

发现writeFile()的参数和write()的参数是对应的,只不过getFileName()函数将原本的$filename变为了sess_$name
再查找write()用法
出现了两个结果,但是根据相关性判断,以下函数更有可能成为调用链
vendor/topthink/framework/src/think/session/Store.php

再跟踪sessionid参数,它是getId()的返回值,这是一个getter方法
vendor/topthink/framework/src/think/session/Store.php

我们找他的setter方法
vendor/topthink/framework/src/think/session/Store.php

这里对id进行了一个简单的校验,仅仅只要求$id===32
再查找setId()的用法
vendor/topthink/framework/src/think/middleware/SessionInit.php

跟踪参数$sessionId
一直跟进发现$sessionId就是cookie中"PHPSESSID"字段的值
$sessionId = $request->cookie($cookieName);
$cookieName = $this->session->getName();
return $this->name //getName()
protected $name = 'PHPSESSID';
这样看文件的路径就可控了,而且还没有过滤。
$data参数
跟踪这个参数和$path参数一样
关键点再下面这里
vendor/topthink/framework/src/think/session/Store.php

跟到这里发现,这个$data是一个默认的空数组,它是由一个setter方法来赋值的,但是全局找不到setedata()的调用,最后发现它是由set()方法赋值的
vendor/topthink/framework/src/think/session/Store.php

全局查找set()用法
vendor/topthink/framework/src/helper.php

发现session()函数调用了set()方法,并且第二个参数就会赋值给$data,而session()在入口函数进行了调用
app/controller/Index.php

也就是可控参数b

漏洞复现
poc
?a=1&b=123<?php+phpinfo();?>
cookie: PHPSESSID=/../../../public/12345678901.php

修复方法
vendor/topthink/framework/src/think/session/Store.php
修复前

修复后

给$id多加了一个函数来校验
根据php手册的解释,这个函数会检测他的参数是否仅仅只包含字母和数字

就意味着我们不能传入特殊符号,也就没办法传入php代码

浙公网安备 33010602011771号