back to writeups

Stream

Hack The Box · Season 12 · completed 11 October 2026
Machine rank #643 NEW Linux Medium Season 12 #643 solve Spring Boot ClickHouse XXE User Root ● Completed
Responsible disclosure note This public portfolio summary documents the authorised Hack The Box lab path. Passwords and weaponised payloads are intentionally omitted.
TL;DR Exposed Spring Boot heap dump → service credentials → ClickHouse raw/XML and parsed/JSON pipeline analysis → XXE file disclosure → SSH foothold → Kopia snapshot log recovery → privileged ClickHouse access → root-owned DLQ formatter trust-boundary abuse → root.
Initial access
Spring Boot actuator heap dump
HTB solve rank
Player #643 to solve Stream
Data platform
ClickHouse-backed AIDX ingestion pipeline
Foothold
XXE file disclosure and recovered SSH access
Privilege escalation
Database-controlled formatter executed by a root timer

01Heap dump discovery

The API virtual host exposed a minimal Spring Boot actuator surface, including an unrestricted heap dump. Parsing the HPROF revealed live Hikari connection material for the event-ingestion database.

curl -sS http://api.htb-airport.htb/actuator
curl -sS http://api.htb-airport.htb/actuator/heapdump -o api-heapdump.hprof

java -jar JDumpSpider.jar api-heapdump.hprof

This was a reminder that heap dumps are secrets-bearing artifacts: they can retain active connection objects, usernames, passwords, tokens and application configuration.

02ClickHouse and the AIDX pipeline

The recovered service account authenticated to ClickHouse. Schema inspection showed a raw Kafka-backed XML feed alongside a normalized JSON view:

partner_feed.flightleg_raw
partner_feed.flightleg_raw_kafka
partner_feed.flightleg_raw_mv
partner_feed.flightleg_json
partner_feed.flightleg_json_kafka
partner_feed.flightleg_json_mv

XML submitted to /feeds/aidx was persisted as raw data and also transformed into structured JSON. The structured output became a useful observation point for testing parser behavior safely within the lab.

03XXE file disclosure

The XML parser resolved external entities. By placing an entity in a field retained by the downstream normalizer, local-file content became visible in the JSON table. Reading /etc/passwd identified the service and interactive user accounts, including martin_cole.

The key lesson was not only the XXE itself: an asynchronous pipeline can still be an exfiltration channel when attacker-controlled parsed fields are subsequently stored and queryable.

04User foothold

The identified interactive account used a weak password. After validating the credential against the authorised lab SSH service, I obtained a stable shell as martin_cole.

user.txtcf3dfb6909588e0785334ac14ba0ae52

05Kopia backup recovery

Martin owned a configured Kopia repository containing a snapshot of ClickHouse logs. The repository could be listed and restored through the existing local configuration:

kopia repository status
kopia snapshot list --all
kopia snapshot restore <snapshot-id> ~/restored-ch-logs

A historical ClickHouse error exposed administrative authentication material. The resulting account had broad database privileges, including file and Kafka capabilities.

06Root through a DLQ formatter trust boundary

Systemd exposed a root-owned AIDX dead-letter-queue timer. Its worker consumed messages from Kafka and invoked a Python formatter from ClickHouse's user-files directory. This created a dangerous boundary: a database administrator could alter a file that the root service later executed.

Within the lab, I validated that the formatter was writable through the database's authorised file interface, triggered the DLQ consumer, and waited for the timer. The root execution context then created a privileged local shell.

Design failure A root service must never execute content from a path writable by a lower-privileged database or application account. The formatter should be root-owned, immutable to the database service, and validated before use.
root.txt815dcc47bdaef14495d7b4102dfc1706

07Attack path

exposed Spring Boot heap dump
        ↓
live database credentials
        ↓
AIDX raw XML / parsed JSON pipeline
        ↓
XXE → local account discovery
        ↓
SSH foothold
        ↓
Kopia snapshot → historic privileged database access
        ↓
database-controlled formatter executed by root timer
        ↓
root

08Takeaways

◆If this were my environment

Memory dumps, parser behavior, logs and a privileged dead-letter formatter all became credential or execution paths. I would minimize sensitive data at every stage of the stream-processing pipeline.

Priority controls
  • Restrict heap-dump creation and access, exclude secrets from process arguments and memory where possible, and rotate credentials after diagnostic collection.
  • Disable XML external entities and network resolution, use hardened parsers, and validate input against a strict schema.
  • Redact tokens and passwords from Kopia and application logs; send logs to an append-only store with access controls and retention limits.
  • Run DLQ formatters as unprivileged workers with fixed templates and no shell, filesystem or secret-store 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