The Block City Times
umasscybersec
Task: a token-gated news site chained a Flask launcher, a Spring Boot app, admin bots, file upload, and Actuator exposure. Solution: upload HTML as text/plain for stored XSS, switch the app into dev mode, abuse the report runner, and exfiltrate the FLAG cookie through article tags.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
The Block City Times — UMass Cybersecurity CTF
Challenge Summary
Organizer description was not preserved in the local task files.
The public entrypoint was a Flask and gunicorn gate that required a team-specific CTFd access token to spawn a per-team instance. After creating a live instance, the real target was a Spring Boot news site with file submission, an authenticated editorial bot, Actuator endpoints, and a developer report feature that only appeared in dev mode.
The final exploit was a chained web attack: upload an .html file while claiming text/plain, get stored XSS in the editorial bot, use that admin session to switch app.active-config to dev, trigger the report runner on /api/../files/<payload>, and reuse the same payload in the report-runner browser where a FLAG cookie was present. The flag was exfiltrated by overwriting article 1 tags through the authenticated API.
Reconnaissance
Local validation with docker compose up -d --build reproduced the challenge stack and made the attack path easy to verify before running it remotely. The source tree showed two important components:
- A public Flask gate that created a team-specific host after a valid CTFd token was supplied.
- A Spring Boot application behind that gate, plus two Puppeteer bots: an editorial bot for uploaded stories and a report-runner service for developer diagnostics.
Reading StoryController.java showed that /submit only validated MultipartFile.getContentType() against app.outbound.allowed-types, which included text/plain and application/pdf. However, /files/{filename} later served uploads using Files.probeContentType(filePath), so the response type was recalculated from the stored filename instead of the original claimed type.
...