Every Post, One Id at a Time — JavaScript Bug Hunt
Modelled on the Parler scrape (January 2021): post IDs were sequential, the API required no authentication for reads, and there was no rate limit.
- Language: JavaScript
- Layer: Backend
- Difficulty: Medium
- Concepts: Security, Rate Limiting
- Modelled on: Parler · 2021
- Visible tests: an authenticated caller can read a post; an anonymous caller is refused; the budget stops enumeration
- Reward: 50 XP for a complete fix
Briefing
Modelled on the Parler scrape (January 2021): post IDs were sequential, the API required no authentication for reads, and there was no rate limit. A small group enumerated the entire site — roughly 70 TB — in a couple of days.
feed.js serves any post id to anyone as fast as they can ask.
Fix fetchPost so unauthenticated callers are refused and each client is rate limited.
Bug report
BUG-PARLER · Priority: Critical · Reported by: platform security
fetchPost(store, postId, session, buckets, limit) must return { status, post }:
- "unauthorized" when session is missing or has no clientId
- "rate-limited" when this client has already made
limitrequests - "not-found" when the post does not exist (this still consumes a request)
- "ok" with the post otherwise
buckets maps clientId -> requests made so far.
Observed: no session is required and no request is counted, so the whole archive can be walked by incrementing the id.
Logs
[feed] served post 0000000001..0000998211 to one client in 41 minutes
[feed] no session, no throttle, sequential idsThe code as shipped
src/feed/feed.js (editable)
exports.fetchPost = function (store, postId, session, buckets, limit) {
var post = store[postId];
if (!post) return { status: "not-found", post: null };
return { status: "ok", post: post };
};
Read-only context: src/feed/POLICY.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.