GraphQL
GraphQL exposes a single endpoint (/graphql) where the client decides exactly what data to request. That flexibility is also the surface: the schema is self-describing (introspection), one query can request deeply nested data, and authorization —spread across endpoints in REST— here must be applied field by field. When it isn’t, a resolver with no check leaks whatever you ask for.
Threat model
Section titled “Threat model”The root mistake is treating GraphQL as if the transport layer authorized. It doesn’t: every field and every mutation is a door, and if the resolver doesn’t check permissions, it doesn’t matter that the front-end hides the button. Add introspection (the attacker knows the whole model), batching (brute force in one request), and nested queries (amplification DoS) and the surface is very rich.
Red Team
Section titled “Red Team”Discovery and introspection
Section titled “Discovery and introspection”# is introspection alive?curl -s https://target/graphql -H 'Content-Type: application/json' \ -d '{"query":"{__schema{types{name}}}"}'Full introspection query → every type, field, argument, and mutation. If disabled:
- Field suggestion: GraphQL suggests similar names when you mistype (
Did you mean "user"?) → clairvoyance reconstructs the schema blind. - Common endpoints:
/graphql,/graphiql,/v1/graphql,/api/graphql,/query.
By hand
Section titled “By hand”- Per-field IDOR / BOLA: request objects by someone else’s ID:
{ user(id: 1337) { email passwordHash privateNotes } }- Per-mutation authZ (BFLA): run admin mutations without a role:
mutation { deleteUser(id: 1) { ok } updateRole(user: 2, role: "ADMIN") { ok } }- Batching for brute-force/rate-limit bypass: many operations in a single request → beats per-request rate-limiting:
[{"query":"mutation{login(u:\"a\",p:\"1\"){token}}"}, {"query":"mutation{login(u:\"a\",p:\"2\"){token}}"}, ...]or aliases in a single document:
mutation { a:login(u:"x",p:"1"){token} b:login(u:"x",p:"2"){token} ... }- Resolver injection: arguments reach SQL/NoSQL/commands just like in REST →
{ search(q: "' OR 1=1-- -") }. - Nesting/amplification DoS: deep circular relations (
posts{author{posts{author{...}}}}) hammer the backend with no depth/cost limit.
- InQL (Burp), GraphQL Voyager — visualize/explore the schema.
- clairvoyance — reconstruct the schema without introspection.
- graphql-cop, graphw00f — audit and engine fingerprint.
- Burp Repeater with
Content-Type: application/json.
Impact
Section titled “Impact”Mass data leakage via unauthorized fields, admin actions via uncontrolled mutations, rate-limit bypass via batching, DoS via nested queries, inherited injections in resolvers.
Blue Team
Section titled “Blue Team”Detection
Section titled “Detection”- Introspection queries (
__schema,__type) in production. - Requests with hundreds of aliases or arrays of operations (batching abuse).
- Anomalous nesting depth; mass field-suggestion errors.
Telemetry
Section titled “Telemetry”Log operation, depth, computed cost, and user per query; alert on introspection and batching.
Hardening
Section titled “Hardening”- Field/resolver-level authorization, not endpoint-level; never trust that the client won’t request a field.
- Disable introspection and field suggestions in production.
- Depth and cost limits (query cost analysis); limit aliases and operations per batch.
- Rate-limit that counts operations, not just HTTP requests.
- Persisted queries (allowlist of permitted operations) for internal APIs.
- Validate and parameterize arguments like any untrusted input.
Response
Section titled “Response”Revoke tokens, audit which fields/objects were extracted, enable cost limits, and disable introspection if it was open.
CVEs and real-world cases
Section titled “CVEs and real-world cases”- HackerOne / GitLab / Shopify — many public reports of BOLA/BFLA from missing field-level authorization in GraphQL.
- CVE-2023-xxxx (Hasura / Apollo) — introspection and access-control bugs in popular engines.
- Facebook / Instagram — historical data leaks from resolvers with no permission check.
- PortSwigger and the GraphQL security community document batching brute force and nesting DoS.
Testing checklist
Section titled “Testing checklist”- Is introspection enabled in production?
- If not, reconstructible via field suggestions / clairvoyance?
- Are fields/objects authorized by someone else’s ID? (BOLA)
- Do admin mutations check role? (BFLA)
- Does batching/aliases bypass the rate-limit?
- Is there a query depth and cost limit? (DoS)
- Do resolver arguments reach SQL/NoSQL/commands unsanitized?
- Do errors leak stack traces or internal data?