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