Meta paid a $78,000 bug bounty for a broken access control flaw in its customer support infrastructure. Sequential case IDs + missing server-side ownership checks on GraphQL endpoints meant an attacker could increment a number and read someone else's support tickets — including chat transcripts, uploaded files, and PII. No exploit code, no malware, just a logic flaw. This post breaks down the mechanics, the attack chain, and how to actually prevent it in your own APIs.
What Happened
A security researcher, Rony K Roy, was testing Meta Horizon Managed Solutions — the enterprise console used to manage Meta Quest devices and accounts — and found what looked like a low-severity permissions bug in January 2026.
As he dug in, the scope grew. The same broken authorization logic reached into Meta.com's support system, live customer support chat, and internal case management tooling.
The root cause: several GraphQL resolvers never verified that the requesting user actually owned the object they were requesting. Combined with sequentially assigned case IDs, this is a textbook IDOR (Insecure Direct Object Reference) — CWE-639, often paired with Broken Access Control (CWE-284) and Missing Authorization (CWE-862).
What was exposed
- Full support email threads
- Live chat transcripts with agents
- Case notes and escalation metadata
- Uploaded attachments
- PII shared in tickets (names, emails, phone numbers)
It wasn't just read access — the same gap allowed unauthorized write actions: creating cases on behalf of other orgs, changing case status, and adding external "subscribers" to cases they had no relationship to.
Meta shipped fixes by April 2026, confirmed no evidence of active exploitation, and paid out $78K. Roy is now on Meta's 2026 top researchers leaderboard.
Authentication vs. Authorization (the distinction that keeps biting teams)
Authentication answers: "who are you?"
Authorization answers: "should you be allowed to see THIS specific object?"
Meta's users were fully authenticated — real accounts, valid sessions. The backend just never checked whether the specific case ID being requested belonged to the requesting user. This is the same flaw class behind the 2018 Facebook access token breach (~50M accounts), and it keeps showing up in REST APIs, mobile backends, and now GraphQL layers.
How the Attack Chain Would Work
Purely for defensive understanding:
- Recon — Open a legitimate ticket, observe the case ID format and GraphQL query shape.
- Parameter tampering — Swap the case ID in the request to a neighboring value.
- Response validation — Check whether the server returns another user's data instead of a 403/access-denied.
- Enumeration — Script a loop across the ID range to harvest data at scale.
- Privilege abuse — Use the same broken check to modify case status or attach unauthorized subscribers.
No RCE, no injection, no credential theft — just a business-logic gap that most automated scanners (tuned for known CVEs) won't catch.
Detection Signals to Watch For
Since this lives at the application logic layer, you won't find a signature for it. Look for behavioral anomalies instead:
- One session requesting a wide, sequential range of object IDs in a short window
- High-volume queries against support/case endpoints from a single account
- Object ID requests with no match to any ticket the account has opened
- Case status changes or subscriber additions with no matching agent action in the audit log
- Full-payload responses (PII, attachments) for objects outside a user's own scope
Fixing It: 6 Things That Actually Help
// Bad: trusts the client-provided ID with no ownership check
const case = await db.supportCase.findById(input.caseId);
return case;
// Good: verify ownership server-side before returning anything
const case = await db.supportCase.findById(input.caseId);
if (case.ownerId !== context.userId) {
throw new ForbiddenError();
}
return case;
- Deny by default, authorize explicitly — every resolver checks ownership server-side, always.
- Kill predictable IDs — use UUIDv4 for sensitive objects instead of sequential integers. This doesn't fix authorization on its own, but it kills trivial enumeration.
- Centralize authorization logic — one shared, audited layer every microservice calls into, instead of each team reimplementing checks inconsistently.
- Test authorization in CI/CD — "User A tries to access User B's object" should be a standard regression test, not something only bug bounty hunters catch.
- Rate-limit and monitor object enumeration — sequential/high-volume ID requests are one of the most reliable IDOR signals in production logs.
- Re-test "low severity" access control reports — this bug was initially triaged as low-risk. Severity often depends on scale, and scale isn't obvious on first report.
Takeaway
This wasn't a zero-day or a nation-state exploit — it was a support ticket counter going up by one. If you're shipping GraphQL or REST APIs with any kind of sequential ID, this is a good week to go verify that "logged in" and "authorized" aren't being treated as the same thing in your codebase.
Full write-up with diagrams and testing methodology (Burp Suite, manual ID swapping, etc.):
👉 https://www.xpert4cyber.com/2026/07/meta-bug-bounty-support-chat-exposed.html
Curious how other teams handle this — do you run automated "cross-user object access" tests in CI, or is this still mostly caught manually in your org?
Top comments (0)