Skip to content

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.

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.

# 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.
  • 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.

Mass data leakage via unauthorized fields, admin actions via uncontrolled mutations, rate-limit bypass via batching, DoS via nested queries, inherited injections in resolvers.

  • Introspection queries (__schema, __type) in production.
  • Requests with hundreds of aliases or arrays of operations (batching abuse).
  • Anomalous nesting depth; mass field-suggestion errors.

Log operation, depth, computed cost, and user per query; alert on introspection and batching.

  • 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.

Revoke tokens, audit which fields/objects were extracted, enable cost limits, and disable introspection if it was open.

  • 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.
  • 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?