GraphQL Pentest Methodology: Introspection to Field Auth
Cybersecurity
GraphQL endpoints expose a different attack surface than REST. This playbook covers introspection harvesting, depth and complexity attacks, batching abuse, and the field-level authorization holes that scanners miss.
By Arjun Raghavan, Security & Systems Lead, BIPI · January 2, 2025 · 9 min read
GraphQL trades many small REST endpoints for one big query language, which means scanners miss most of the surface. A proper GraphQL pentest starts with schema discovery and ends with per-field authorization, with a half-dozen abuse vectors in between.
Schema discovery
Send the standard introspection query to /graphql, /api/graphql, /v1/graphql, and /query. If introspection is disabled, run Clairvoyance to brute-force field names from a wordlist, and InQL to rebuild the schema from observed traffic. GraphQL Voyager renders the schema as a graph so you can spot privileged mutations like adminUpdateUser or impersonate next to public-facing queries.
- Hit /graphql with {"query":"query{__schema{types{name fields{name}}}}"} for full introspection.
- If blocked, try query whitespace tricks: query {__schema{types{name}}} with newlines, or POST as application/graphql instead of application/json.
- Use graphql-cop for a fast nuclei-style sweep of common misconfigs.
- Caido's GraphQL Raider tab streamlines repeated query mutation across cookies and headers.
How to test the abuse vectors
Once you have the schema, the attack surface splits into resource exhaustion, authorization, and injection. Each has a distinct request pattern.
Depth attacks: build a query that recurses through reciprocal relationships, like user { posts { author { posts { author { posts { author { ... }}}}}}}. Twenty levels of nesting can lock up a GraphQL server because each level multiplies database joins. Complexity attacks: request large lists with first: 10000 on every list field. Field duplication: alias the same expensive field 1000 times in a single query, since most resolvers are not deduplicated.
Authorization holes
GraphQL field-level authorization is where real bounties hide. The pattern: resolver A authorizes correctly on the parent type, but a nested resolver B trusts the parent and skips its own auth check. Test by querying a public type and walking to a sensitive field via a relationship.
- Query a public Post and walk to post.author.email or post.author.passwordResetToken.
- Mutation argument tampering: pass userId of another user to updateProfile and check whether the resolver re-validates ownership.
- Use aliases to call the same mutation 100 times in one request: a1: login(...), a2: login(...) for credential brute force without tripping rate limits.
- Test directives like @skip(if: false) and @include(if: true) for filter-bypass behavior.
Detection
Log every GraphQL operation by name and depth. Alert on operations exceeding configured depth or complexity, on operations with more than 10 aliases, and on introspection queries from non-developer IP ranges. Apollo Studio and Hasura's metrics endpoint surface these natively. For self-hosted Apollo, add a plugin that emits parsed query depth to your SIEM.
Remediation
- Disable introspection in production via NODE_ENV gates or an explicit flag like introspection: false in ApolloServer config.
- Enforce depth limits with graphql-depth-limit set to 7, and complexity limits with graphql-query-complexity set to 1000.
- Adopt persisted queries: clients send a hash, server looks up the pre-registered operation. Unknown hashes are rejected, killing arbitrary query attacks.
- Implement per-field authorization with graphql-shield, GraphQL Armor, or directive-based auth like @auth(requires: ADMIN). Authorize at every resolver, never trust the parent.
- Disable batched queries unless you need them. If you do, count operations and aliases against the rate limit, not just HTTP requests.
Validation
Re-run InQL against staging with introspection disabled and confirm the schema endpoint returns errors. Send a depth-15 query and verify rejection. Test alias-based credential stuffing and confirm the rate limiter counts aliases. BIPI's GraphQL engagements walk every query and mutation in your schema and produce a per-field authorization matrix you can use as a regression test going forward.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.