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/
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.
...
Permission denied (requires auth)
Sign in to read this free writeup
This writeup is free — just sign in with GitHub to read it.
$ssh [email protected]