How the attack works

GraphQL uses a single endpoint (often /graphql), to which clients send arbitrarily structured queries (reads) and mutations (writes). Several mechanisms get abused.

  • Introspection - a built-in feature through which the field __schema makes the entire schema, including all types, fields, mutations, and descriptions, queryable; if it stays active in production, an attacker can read out the complete API map.
  • Aliases - named fields allow the same field to be called multiple times within a single query, working around the restriction that an object can't have two fields with the same name.
  • Batching - multiple operations are bundled into a single HTTP request. Both bundle many operations into one HTTP message, letting them bypass rate limiters that only count HTTP requests, ideal for brute forcing logins, OTP/2FA codes, or discount codes.
  • DoS - queries have a depth (nested objects) and a volume (e.g. first: 99999999); without depth, volume, or cost limits, a single expensive query can overload the database and server.
  • Missing authorization - if the API delivers objects directly via IDs passed as arguments without checking whether the caller has access, IDOR/BOLA results; authorization must be checked per object and per field.
1. Starting pointGraphQL endpoint openly reachable
→
2. AttackIntrospection, batching, or expensive nested queries
→
3. ResultSchema leak, brute force, or denial of service

Flexible queries without limits become an attack surface.

Example

GraphQL query
query isValidDiscount($code: Int) {
  isValidDiscount(code:$code){
    valid
  }
  isValidDiscount2:isValidDiscount(code:$code){
    valid
  }
  isValidDiscount3:isValidDiscount(code:$code){
    valid
  }
}

Impact

Open introspection and active "Did you mean" field suggestions make it easier to reconnoiter the entire API, including unintentionally exposed private fields (e.g. email addresses, user IDs).

Batching/alias attacks enable fast, hard-to-detect brute forcing of passwords, 2FA/OTP codes, tokens, and enumeration of objects, and in the process frequently bypass rate limiters and protection mechanisms that only count HTTP requests, since only a single request is visible.

Expensive or deeply nested queries lead to denial of service (exhaustion of CPU, memory, database, or downstream services).

Missing object-/field-level authorization allows unauthorized data access and write access (IDOR/BOLA/BFLA), in the worst case up to privilege escalation.

How to protect yourself

  • Disable introspection and schema explorers such as GraphiQL in production, or for unauthenticated/unauthorized users (system-wide if the API is only used internally).
  • Disable field suggestions ("Did you mean ...?" suggestions) so the schema can't be guessed even with introspection disabled.
  • Limit batching/alias attacks: object-level rate limiting in code, plus limiting the number of operations executed simultaneously (per OWASP not a silver bullet, but an effective measure).
  • Prevent DoS: set a depth limit and a volume limit, enforce pagination, cap maximum query size, use query cost analysis with a maximum cost limit, plus timeouts at the application level.
  • Enforce authorization at the object and field level (RBAC), on both edges AND nodes; possessing an object ID must never imply access (against IDOR/BOLA/BFLA).
  • Strict input validation with an allowlist and specific GraphQL types (scalars/enums, custom validators) against injection and DoS.
  • Don't return excessive error messages (disable debug mode and stack traces in production).
  • Prevent CSRF: use cross-site request forgery protection (validation of content type/request format, CSRF token).

Sources

Keep reading