networkfreemedium

security-breach-ruin

umdctf

Task: source code and shell access exposed an internal HTTPS dashboard where attacker-controlled filter text and the secret flag were gzip-compressed into the same JSON response. Solution: become on-path with bidirectional ARP spoofing, measure encrypted response sizes, and brute-force the flag with a BREACH-style length oracle.

$ ls tags/ techniques/
arp_spoofingmitm_packet_capturetcp_length_oraclemedian_based_bruteforce

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

security-breach-ruin — UMDCTF

Description

Recover the flag from the internal HTTPS dashboard using the provided source code and shell access on 10.0.0.3.

The challenge gave server.py, admin.py, and a shell on 10.0.0.3. A local helper exploit script also exists at tasks/umdctf/security-breach-ruin/solve.py.

Analysis

Reading the source showed a classic BREACH-style compression side channel. The server returned gzip-compressed JSON from /api/dashboard:

{"filter":"<attacker>","flag":"<secret>"}

The attacker controlled latest_suggestion through POST /api/suggestions, and the admin polled /api/dashboard over HTTPS about every 50 ms with Accept-Encoding including gzip. That meant a correct flag prefix inside filter should compress better against flag, making the encrypted response slightly shorter.

The first obstacle was visibility. Shell access was only on 10.0.0.3, and direct sniffing failed because unicast traffic between 10.0.0.1 (admin) and 10.0.0.2 (server) was not naturally visible from our host.

Exploit Summary

I used bidirectional ARP spoofing to become the man in the middle:

arpspoof -t 10.0.0.1 10.0.0.2 arpspoof -t 10.0.0.2 10.0.0.1

With IP forwarding already enabled, tcpdump could then capture the inbound HTTPS packets. The oracle was built by summing server -> admin TCP payload lengths between consecutive admin -> server request packets. When the guessed prefix was correct, gzip achieved a better match and the encrypted response became smaller.

To recover the flag reliably, I brute-forced characters from [a-z0-9_}] starting from the known prefix UMDCTF{. Because packet sizes were noisy, each candidate was measured across repeated admin polls and ranked by median response length.

Solution

...

$ grep --similar

Similar writeups