access.log, and someone (the box, or a prior attacker on this shared instance) has already left a stale exit marker in that log that blocks the gadget from re-triggering. Switching to a session-file poisoning variant of the same CVE sidesteps the log entirely and lands RCE as www-data. From there a .env file hands over the Craft database credentials in plaintext, a SELECT against the users table pulls a bcrypt hash, and rockyou cracks it in seconds. That's SSH as adam and the user flag — but root is the actual puzzle: a GNU Inetutils telnetd is listening on loopback only, and its ancient handling of the telnet USER environment variable (CVE-2026-24061) lets a client hand it -f root and get logged straight in as root, no password asked.
Just SSH and a web front end — nothing hiding behind a vhost this time.
The site on 80 is a Craft CMS install. /admin/login leaks the exact build in its footer/asset paths:
Craft ≤5.6.16 is vulnerable to CVE-2025-32432, an unauthenticated RCE in the asset-transform generation feature. The root cause is a Yii2 gadget chain: Craft's session handler is backed by yii\web\PhpManager-style file storage, and asset-transform generation can be coerced into unserializing attacker-controlled data from that storage, which — through the right gadget — ends in arbitrary PHP execution.
The natural PoC (cd-ratel/CVE-2025-32432) works by writing a PHP payload into /var/log/nginx/access.log via a crafted User-Agent, then triggering the asset-transform gadget so it include()s the log file and runs the injected code between output markers. It failed on every asset ID:
The log itself was the problem: access.log already carried a stale exit; statement from a previous exploitation attempt (this is a shared HTB instance, so someone hit it before me), which short-circuits PHP execution before the gadget's own marker-wrapped payload ever runs. Log-poisoning PoCs are fragile exactly because they depend on a file they don't control the full contents of.
Switching to c0gnit00/CVE-2025-32432 ("CraftCMS2Shell") sidesteps the log path entirely — it poisons Craft's own PHP session file (/var/lib/php/sessions/sess_<id>) instead, which nothing else on the box is writing to, and auto-brute-forces the asset ID it needs:
RCE as www-data, and Craft's own .env in the web root hands over the database credentials outright:
Root MySQL creds. Straight to the users table for a hash to crack:
Bcrypt cost 13 is slow, but the CMS admin behind a "simple" box usually reuses an early rockyou entry. It does:
adam : darkangel — and Craft's own admin account email doubled as the system username, so no guessing needed there either.www-data couldn't read /home/adam directly (mode 750, owned adam:adam), so this credential is the only way in. SSH confirms it and drops the user flag:
sudo -l is a dead end — adam has no rule, not even a NOPASSWD one — and /root/root.txt is a flat permission denied. No SUID oddities either; the box wants a service, not a local binary. Checking listeners turns up something on loopback:
A telnet daemon reachable only from the box itself is a strong tell. Checking what's actually listening:
GNU Inetutils telnetd 1.9.3–2.7 is vulnerable to CVE-2026-24061 (CVSS 9.8): it honors the telnet client's ENVIRON negotiation and passes whatever the client claims as the USER variable straight into login as an argument, unsanitized. A client that negotiates USER=-f root makes telnetd run:
login -f is meant for trusted callers like getty, not for an argument a remote telnet client gets to choose. The daemon never sanity-checks it, so it's an authentication bypass disguised as an argument-injection bug.
The netkit telnet client's -l flag is meant to do exactly this (telnet -l '-froot' 127.0.0.1 23), but piping commands into it non-interactively through an SSH exec_command channel doesn't work — the client never allocates a real TTY, the telnet IAC/ENVIRON negotiation gets eaten before login can happen, and the connection just closes. Scripting the raw socket protocol by hand from an SSH heredoc hit the same wall for the same reason: no PTY, negotiation half-completes, nothing comes back.
Searching GitHub for a ready PoC turned up one that looked promising and turned out to be a landmine: jacubes/CVE-2026-24061 is not a telnet exploit at all — its rce.py calls out to _fetch_bytes(url, api_key) → _load_module_memory(...) → bootstrap(...), i.e. it downloads and executes attacker-controlled code from a remote server the moment you run it. Declined — that's a backdoor wearing a CVE number, not a PoC.
exec/eval/dynamic import isn't reproducing the vulnerability — it's asking you to trust an unknown third party with code execution on the box (and possibly your own machine, depending on where it runs).The real fix was a PoC that implements the telnet protocol negotiation properly instead of relying on the interactive client — Ish3ng0m4/CVE-2026-24061-Telnetd speaks raw IAC/DO/WILL/SB ENVIRON over a plain socket, injects USER=-f root in the subnegotiation, and reads the shell straight off the wire — no TTY required:
adam was always going to get there once he had a shell at all. The only real fix is not running telnetd — SSH has made it obsolete for exactly this class of bug.No Metasploit on the attack host, so both CVEs ran through standalone Python PoCs rather than a module. The pattern that mattered most was making everything scriptable and non-interactive:
-c CMD mode returns command output directly in the HTTP response — no need for a reverse shell just to read a .env file or query MySQL. Kept the whole enumeration phase to one-shot curl-style calls.ssh CLI. Once creds for adam were in hand, a thin ssh_run.py wrapper (paramiko, one exec_command per call) made every follow-up command a single scriptable call instead of a pexpect-style interactive session.telnet (brew, netkit-derived) run through a non-PTY SSH channel never completes protocol negotiation — the -l flag's ENVIRON handshake gets lost. A PoC that does the IAC/ENVIRON negotiation itself over a raw socket sidesteps the whole TTY problem, which is why the working exploit is a from-scratch protocol client rather than a scripted invocation of the system telnet binary.| → www-data | Craft CMS 5.6.16 pre-auth deserialization RCE via a Yii2 PhpManager gadget chain in the asset-transform pipeline (CVE-2025-32432 / CWE-502); a plaintext .env sat inside the web root with live database credentials |
| → adam | DB creds from .env reused to read the users table; the admin's bcrypt password was a dictionary word crackable from rockyou in seconds (CWE-521) |
| → root | GNU Inetutils telnetd (1.9.3–2.7) trusts the telnet client's ENVIRON-negotiated USER value and passes it unsanitized to login, so USER=-f root becomes login -f root — a passwordless root session (CVE-2026-24061 / CWE-287, CWE-88) |