Finding XSS in a million websites (cPanel CVE-2023-29489)

Finding XSS in a million websites (cPanel CVE-2023-29489)

cPanel is a web hosting control panel software that is deployed widely across the internet. To be exact, there are about ~1.4 million installations of cPanel exposed on the external internet at the time of writing this blog post.

We discovered a reflected cross-site scripting vulnerability that could be exploited without any authentication. In addition to this, the XSS vulnerability was exploitable regardless of whether or not the cPanel management ports (2080, 2082, 2083, 2086) were exposed externally. This means that your website on port 80 and 443 was also vulnerable to the cross-site scripting vulnerability if it is being managed by cPanel.

For those that are worried that their website is affected by this vulnerability, please don’t stress. A lot of the cPanel installations on the internet have cPanel’s auto-update functionality enabled, meaning that you may no longer be vulnerable without having to patch yourself. If you do not have this feature set up, please read this link for instructions on how to enable it.

As always, customers of our Attack Surface Management platform were the first to know when this vulnerability affected them. We continue to perform original security research in an effort to inform our customers about zero-day vulnerabilities.

You can read cPanel’s official advisory here.

Getting Familiar with cPanel’s codebase

cPanel has existed for as long as I have in this world (designed in 1996), for just over 27 years. It’s probably one of the oldest pieces of software we have ever audited at Assetnote. In order to perform our testing, we spun up a DigitalOcean droplet with the marketplace image for cPanel on Ubuntu. This was incredibly convenient and easy, cutting down our setup time significantly.

The historic nature of cPanel means that there are a number of paradigms and technologies that it leverages that aren’t necessarily modern. Namely, most of cPanel is written in Perl with some attack surface being Perl compiled to binaries.

When we initially looked at cPanel mid last year, we focused heavily on the binaries that can be accessed in the /cgi-sys/ directory on cPanel’s management ports. These binaries were Perl code that has been compiled to binaries which were intentionally supposed to be called remotely via HTTP requests. Understanding the logic of these binaries was difficult and while we found several avenues for exploitation, we found that mitigations had been built in that prevented us from successfully exploiting these potential issues.

Outside of these binaries inside this directory, we also noticed that cPanel’s core functionalities and web application are serviced through the cpsrvd binary. This binary is listening on the following ports:

root@vm:~# lsof -Pi | grep cpsrvd
cpsrvd    43328     root    3u  IPv4 218993      0t0  TCP *:2082 (LISTEN)
cpsrvd    43328     root    4u  IPv4 218994      0t0  TCP *:2086 (LISTEN)
cpsrvd    43328     root    5u  IPv4 218995      0t0  TCP *:2083 (LISTEN)
cpsrvd    43328     root    6u  IPv4 218996      0t0  TCP *:2087 (LISTEN)

cPanel leverages Apache’s reverse proxy functionalities and the configuration for this can be found here: /etc/apache2/conf/httpd.conf. This file provided us with a lot of clues and context around what to look for inside the cPanel codebase as there was a lot of proxying and script aliases that defined interesting attack surface:

ProxyPass /cpanelwebcall/ http://127.0.0.1:2082/cpanelwebcall/ max=1 retry=0

... omitted for brevity ...

ScriptAlias /.cpanel/dcv /usr/local/cpanel/cgi-priv/get_local.cgi

RewriteEngine On

RewriteCond %{REQUEST_URI} ^/.well-known/acme-challenge/[0-9a-zA-Z_-]+$ [OR]
RewriteCond %{REQUEST_URI} ^/.well-known/cpanel-dcv/[0-9a-zA-Z_-]+$ [OR]
RewriteCond %{REQUEST_URI} ^/.well-known/pki-validation/[A-F0-9]{32}.txt(?: Sectigo DCV)?$ [OR]
RewriteCond %{REQUEST_URI} ^/.well-known/pki-validation/(?: Ballot169)?
RewriteRule ^ /.cpanel/dcv [passthrough]

The above is just a small excerpt of the httpd.conf file. If you’re considering auditing cPanel we recommend going through all of the different proxy rules inside this file as a starting point.

Thankfully, cPanel does ship with a huge chunk of its source code, which can be obtained from a post-installation cPanel instance at the location /usr/local/cpanel/. While this is also where most of the binaries exist, you can still understand some of the logic of cPanel through reading the libraries and perl code from this directory.

While the perl code inside that folder contains a huge number of clues as to how routing works, the source of truth is still the cpsrvd binary, which is ultimately a compiled version of all of the perl. The source code found in the /usr/local/cpanel directory did not seem like a complete picture of the true functionalities provided by cPanel. Nonetheless, we were grateful of this as it gave us enough context to find the XSS vulnerability.

Deconstructing Httpd.pm

While trying to understand the routing of cPanel, we came across Cpanel/Server/Handlers/Httpd.pm which contained some very specific handling of certain paths. This logic allowed us to map out and understand some of the pre-authentication attack surface without having to understand the binaries.

The comments at the top of the file gave us a good overview of what this handled:

=head1 DESCRIPTION

This module implements a tiny HTTP server in cpsrvd that executes a
whitelist of functionality.

This is useful for contexts where no other service is running on the standard
HTTP and HTTPS ports.

It implements the following behaviors:

=over

=item * Any request whose path is under F</.well-known> is served as a
static file. The path on disk that’s loaded is the same as under Apache.

=item * Any other request whose C<Host> header starts with one of the
following is served as appropriate: C<cpcalendars.>, C<cpcontacts.>,
C<autodiscover.>, C<autoconfig.>. The latter two are only served if
they are enabled in the server configuration.

Note that C<cpanel.>, C<whm.>, and C<webmail.> are NOT handled here.
This is largely because the relevant applications are under base cpsrvd
and thus not (readily) callable from this module, so we have to handle
those applications separately.

The following functionalities were found inside this Perl code:

  • Handling subdomain/hostname based routing to certain services
    • cpcalendars => 'proxy_cpcalendars_cpcontacts'
    • cpcontacts => 'proxy_cpcalendars_cpcontacts'
    • autodiscover => 'autodiscover'
    • autoconfig => 'autoconfig'
  • Handling static paths
    • /img-sys/
    • /sys_cpanel/
  • Handling redirects for /cpanel, /whm, /webmail
  • Handling BoxTrapper requests /cgi-sys/bxd.cgi
  • Handling Dynamic DNS related functionality, specifically calls to /cpanelwebcall/

In addition to Httpd.pm, we also spent a significant amount of time on Cpanel/Server/Handlers/comet.pm which handles websocket messages. There was a lot of interesting functionality that seemed like it could lead to an arbitrary file write, but during the time we had allocated for cPanel research, we were unsuccessful in finding a sink with any meaningful control.

Finding the Cross-Site Scripting

Inside Httpd.pm, we can see the following snippet of code:

    elsif ( 0 == rindex( $doc_path, '/cpanelwebcall/', 0 ) ) {

        # First 15 chars are “/cpanelwebcall/”
        _serve_cpanelwebcall(
            $self->get_server_obj(),
            substr( $doc_path, 15 ),
        );
    }

This meant that any path starting with /cpanelwebcall/ would be routed to _serve_cpanelwebcall, along with the characters after the directory name.

The _serve_cpanelwebcall function looked like this:

sub _serve_cpanelwebcall ( $server_obj, $webcall_uri_piece ) {
    require Cpanel::Server::WebCalls;
    my $out = Cpanel::Server::WebCalls::handle($webcall_uri_piece);

    $server_obj->respond_200_ok_text($out);

    return;
}

This led us to the function Cpanel::Server::WebCalls::handle as the sink:

sub handle ($request) {

    my $id = extract_id_from_request($request);
    substr( $request, 0, length $id ) = q<>;

    Cpanel::WebCalls::ID::is_valid($id) or do {
        die _http_invalid_params_err("Invalid webcall ID: $id");
    };

This leads to:

sub _http_invalid_params_err ($why) {
    return Cpanel::Exception::create_raw( 'cpsrvd::BadRequest', $why );
}

We can trace this back to Cpanel/Exception/cpsrvd/BadRequest.pm. This is where the trail goes cold. Even though we have a lot of Perl source code available, this entire chain was not able to be traced end to end as we were missing the call to Httpd::ErrorPage.

We worked out that the sink was in Cpanel::Server::Handlers::Httpd::ErrorPage which we do have the source for, but we were unable to find the source code that initializes this Perl code. We suspect that this is embedded within the cPanel binary and hence why we were unable to pin point where this code was being called.

Digging into the Httpd::ErrorPage code, we can see that it takes in a variable called message_html which is the sink for this vulnerability. This variable is not sanitised in any way in vulnerable versions of cPanel.

Looking at the patch diff from the latest version of cPanel which has resolved this vulnerability, we can see that the following two lines were added to Cpanel/Server/Handlers/Httpd/ErrorPage.pm:

++ use Cpanel::Encoder::Tiny               ();	

... omitted for brevity ...

++ $var{message_html} = Cpanel::Encoder::Tiny::safe_html_encode_str( $var{message_html} );

Proof of Concept

Putting everything we learnt above together, we can come up with a simple proof of concept such as the one below:

http://example.com/cpanelwebcall/<img%20src=x%20onerror="prompt(1)">aaaaaaaaaaaa
http://example.com:2082/cpanelwebcall/<img%20src=x%20onerror="prompt(1)">aaaaaaaaaaaa
http://example.com:2086/cpanelwebcall/<img%20src=x%20onerror="prompt(1)">aaaaaaaaaaaa
http://example.com:2082/cpanelwebcall/<img%20src=x%20onerror="prompt(1)">aaaaaaaaaaaa

... potentially other ports ...
 

Impact

The impact of this vulnerability is that we are able to execute arbitrary JavaScript, pre-authentication, on almost every port of a webserver using cPanel within its default setup.

This is because of the proxy rules discussed earlier in the blog post. Even on port 80 and 443, we are able to reach the /cpanelwebcall/ directory as it is being proxied to the cPanel management ports by Apache.

Because of this, an attacker can not only attack the management ports of cPanel but also the applications that are running on port 80 and 443.

Due to the fact that the cPanel management ports are vulnerable to this cross-site scripting attack, an attacker could leverage this vulnerability to hijack a legitimate user’s cPanel session.

Once acting on behalf of an authenticated user of cPanel, it is usually trivial to upload a web shell and gain command execution.

Remediation and Timelines

This vulnerability can be remediated by upgrading to any of the following cPanel versions or above:

  • 11.109.9999.116
  • 11.108.0.13
  • 11.106.0.18
  • 11.102.0.31

The timeline for disclosure can be found below:

  • Jan 23rd, 2023: Disclosure of the XSS vulnerability to cPanel via security@cpanel.net.
  • Jan 23rd, 2023: Confirmation from cPanel that they have received the vulnerability and are investigating further.
  • Feb 12th, 2023: Request for updates from Assetnote side
  • Feb 13th, 2023: Vulnerability confirmed by cPanel and assigned SEC-669. Targeted security fix release to follow in a few weeks.
  • March 1st, 2023: Vulnerability fixed and public disclosure released on cPanel website.

cPanel were excellent to work with and remediated this issue within a reasonable time frame after we had disclosed this vulnerability to them.

Conclusion

cPanel has a vast attack surface and it needs more attention from the security researcher community. One of the big blockers during our research of cPanel was the binaries that had been compiled to Perl. We believe that there are more serious bugs yet to be found within these binaries, although, they are quite painful to work with from a reverse engineering perspective.

Our anaylsis of the cpsrvd binary found that most endpoints were not accessible pre-authentication, however, there are plenty of binaries inside the /cgi-sys/ directory that could use more attention.

This was one of the first cases where we had reported a bug to a vendor that had a working and mostly default implementation of auto-updates for their software. This mitigation alone has protected a majority of cPanel websites from this vulnerability in the wild.

That being said, it is still possible to find a large number of cPanel websites that are still vulnerable to this as they did not have the auto-update feature enabled.

[Source]