Unauthenticated IDOR on /api/notes exposes private user notes (PII)
auth-bypass · server.js:48-53
15b34d5cc6 by 42nights on 2026-06-03Summary
The /api/notes endpoint in server.js looks up a note by the numeric query parameter `id` and returns the entire note object regardless of its `private` flag or `owner` field. No authentication, session check, or ownership verification is performed before the response is sent. A request such as `GET /api/notes?id=2` returns alice's note marked `"private":true` containing sensitive data (e.g., bank PIN), confirming the bug.
Impact. Any unauthenticated remote attacker can enumerate note IDs (1, 2, 3, ...) and retrieve every user's notes, including ones the application explicitly marked private. The validated response leaked a bank PIN; the same flaw trivially leaks SSNs, credentials, or any other PII stored in notes. This is a full confidentiality breach of all stored notes with no skill, tooling, or 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):
Require an authenticated session and enforce that the requesting user owns the note (or that the note is non-private). Example patch:
```diff
if (parsed.pathname === "/api/notes") {
+ const user = getSessionUser(req); // verify session cookie / token
+ if (!user) return send(res, 401, "authentication required");
const id = Number(q.id || 1);
const note = NOTES.find((n) => n.id === id);
if (!note) return send(res, 404, "no such note");
+ if (note.private && note.owner !== user.username) {
+ return send(res, 403, "forbidden");
+ }
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.