open-insight
umdctf
Task: a public Next.js spreadsheet evaluates attacker-controlled formulas inside a fake sandbox and lets users report shared sheets to a moderator bot. Solution: escape the formula runtime via JSON.stringify.constructor, execute same-origin JavaScript in the bot's internal web:3000 context, fetch /admin, and exfiltrate the admin page.
$ 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.
open-insight — UMDCTF
Description
Share your market insights with the world.
The challenge consists of a public Next.js spreadsheet app and a separate bot service that accepts a sheetId via POST /report. The bot claims that a moderator opens and exports the shared workbook in a disposable browser session.
The goal is to turn control over a workbook cell into code execution inside the moderator session, then use that access to retrieve the flag.
Analysis
The public workbook pages embed initialCells directly into the HTML, so any stored formula is evaluated when the sheet is opened. Recon on the downloaded client chunk (sheet_chunk.js) showed that formulas are executed with the following pattern:
Function("S", `with (S) { "use strict"; return (${expr}); }`)(proxy)
The intended sandbox exposes a proxy with selected names such as:
MathJSON.parse/JSON.stringifyNumber,String,Boolean,RegExpparseInt,parseFloat,isNaN,isFinitedocument(proxied)cells(ref)
That is already dangerous, but the core bug is worse: the code tries to enable strict mode inside a with block. That directive is ineffective here, so the attacker-controlled expression runs in sloppy mode.
Once any normal function object is reachable, the sandbox can be escaped through its constructor, which is the global Function constructor. Because JSON.stringify is exposed, this escape is enough:
JSON.stringify.constructor("return this")()
That returns the global object (window). From there, the spreadsheet formula can reach browser APIs such as XMLHttpRequest and navigator.sendBeacon.
Two additional observations shaped the final exploit:
- Saving a sheet is done through a Next server action, but trying to save another user's sheet returns
{"ok":false,"error":"not_owner"}. So the path is not horizontal sheet takeover. - The
openinsight_sessioncookie isHttpOnly, so classic cookie theft from JavaScript is not useful.
...
$ grep --similar
Similar writeups
- [web][free]live-signal— umdctf
- [web][Pro]Lab 60 — CalcForge — RCE via Expression Evaluator Sandbox Escape— hackadvisor
- [network][free]security-breach— umdctf
- [web][Pro]Lab 116 — InsightForge — IDOR via Undocumented Internal API— hackadvisor
- [web][free]Chocolate Drop— alfactf