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.
Flexible queries without limits become an attack surface.
Example
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
- GraphQL Cheat Sheet OWASP Cheat Sheet Series, 2025
- GraphQL API vulnerabilities PortSwigger Web Security Academy, 2025