Add Season 5: Mission Control (SSRF) and Paperwork (Broken Access Control) - #216
Add Season 5: Mission Control (SSRF) and Paperwork (Broken Access Control)#216ggeorgeazevedo wants to merge 6 commits into
Conversation
Vulnerable Express app, three SSRF exploits, unit tests, one solution and two hints. Runs offline: the partner feed and the internal services are started on ephemeral loopback ports by the test files.
An expense reimbursement API in TypeScript with solid authentication and no authorization: a missing ownership check on read, a blind object merge on update, and a role taken from a client-supplied header on approval. Ships with three exploits, unit tests, one solution, two hints and its own tsconfig.
|
Update: @xcorail gave the go-ahead in #213 for a second level, so I've pushed it to this same branch rather than opening a separate PR — the season lands as one reviewable unit instead of two PRs waiting on each other. Season 5 - Level 2: Paperwork — Broken Access Control in TypeScript. An expense reimbursement API where the authentication is solid and the authorization is missing: a read endpoint that checks the session but not the ownership, an update endpoint that merges the request body straight into the record, and an approval endpoint that takes the caller's role from a request header. Three exploits, unit tests, one solution and two hints, all in memory so it runs offline. I've rewritten the description above to cover both levels, including verification steps and a few open questions for you — season naming, level order, and whether you'd rather have Level 2 in plain JavaScript than TypeScript. No rush on review at all, and thanks again for the quick steer on the season structure. |
Summary
Follows up on the proposal in #213. Per @xcorail's guidance there, this is opened as a new season rather than as new challenges inside Season 2, and it now carries the two levels @xcorail green-lit in that thread.
Both cover priority combinations listed in
CONTRIBUTING.md, and both share a shape: the code is not missing a security check, it has one that looks reasonable and does not hold.Changes
Season-5/— a new self-contained season with its ownREADME.md, written so further community levels can be appended asLevel-3,Level-4, … (the open Lua proposal in Adding New Level: Lua Metatable hooks #128 would slot straight in, which I think also matches @jkcso's "appendix" idea).Season-5/Level-1/— Mission Control, Server-Side Request Forgery (CWE-918) injavascript.Season-5/Level-2/— Paperwork, Broken Access Control (CWE-639 / CWE-915 / CWE-602) intypescript.README.md.Each level follows the existing file conventions:
code,hack,tests,solution, twohintfiles, and its ownpackage.json.Level 1 — Mission Control (SSRF,
javascript)A space agency dashboard previews telemetry feeds from partner observatories, and lets an operator paste the URL of a partner that is not registered yet.
The bug.
code.jsscreens that URL against a blocklist of strings (localhost,127.0.0.1,169.254.169.254,metadata). The check runs once, on the raw string, before anything is fetched.hack.jsgets past it three ways:http://127.1:<port>/admin/keysreaches the internal admin API.http://0x7f000001:<port>/latest/meta-data/iam/credentialsreaches the stand-in metadata service.302tohttp://localhost:<port>/admin/keys, andfetchfollows it automatically.The fix.
solution.jsstops letting the caller pick the destination: the request carries a feed id, resolved against the server-side registry that already exists at the top of the file, and redirects are handled manually so the response has to come back from the host that was allowed. A closing comment points at what a service that genuinely must fetch arbitrary URLs would need on top — resolve the hostname, reject private, loopback and link-local ranges, and pin the connection to the address that was validated.Level 2 — Paperwork (Broken Access Control,
typescript)An expense reimbursement API where the authentication is solid and the authorization is missing. Alice and Bob are employees, Carol manages them both. Every endpoint knows exactly who is calling; not one of them checks what that person is allowed to reach.
The bugs. One per endpoint, each a different flavour of the same mistake:
GET /reports/:id— verifies the session, never the ownership. The comment in the code argues the ids are random so nobody can land on someone else's report, which is an argument about how hard a record is to find, not about who may read it. Bob pulls Alice's payout details without guessing anything.PATCH /reports/:id— does check ownership, then doesObject.assign(report, req.body). Owning a record is not owning every field of it: Alice approves her own report by sendingstatus: "approved".POST /reports/:id/approve— reads the role from anx-user-roleheader "to save a lookup". Alice sendsx-user-role: managerand signs off her own spending.The fix.
solution.tsfixes each one at the level it belongs: a singlemayReadhelper both read paths go through, an allow list of editable fields that only applies while the report is a draft, and the role read from the server-side user record — plus the rule that a manager never approves their own report.Verification
Neither level needs outbound network access. Level 1 starts the partner observatory and the internal services on ephemeral loopback ports; Level 2 is entirely in memory. Both are deterministic and work offline inside Codespaces.
Notes for reviewers
Season-5because it is the next number, butCONTRIBUTING.mdearmarks Season 5 for the ProdBot/AI track. Renaming the folder is trivial, so if you would rather call this a bonus season, or reserve 5 for AI, just tell me and I will push the rename.tsconfig.jsonandts-node, since there is no TS anywhere in the game yet. The trade-off is that it runs from inside its own folder (cd Season-5/Level-2 && npm test) rather than from the repo root like the JS levels; the README says so explicitly. Happy to convert it to plain JavaScript if you would rather keep one way of running things.codeandsolutionin Level 1 is done with aTARGETconstant at the top oftests.js/hack.jsinstead of the two commentedrequirelines used elsewhere, because the module has to be required insidebefore()once the mock servers have picked their ports. Let me know if you would rather keep the exact style of the other levels..devcontainer/devcontainer.jsoninstalls dependencies for Seasons 3 and 4 inpostCreateCommandbut not for this season, so players runnpm installthemselves as the READMEs instruct. Say the word if you would like me to add Season 5 to that command.Closes: #213
Task list