GraphQL Hacking: The Complete Practical Guide

Bypassing GraphQL Introspection Defenses

Recover a GraphQL schema when introspection is disabled: field suggestions leak real field names, and fuzzing rebuilds the whole schema from them.

Published

Disabling introspection feels like it closes the schema, but on many servers it does not. Field suggestions leak real names one query at a time, enough to rebuild the schema by fuzzing. This page covers recovering a schema without __schema. Practice on a free live target.

Step 1: confirm introspection is blocked

First verify you actually need the fallback. A blocked introspection query returns an error rather than a schema:

POST /graphql HTTP/1.1
Host: target.example
Content-Type: application/json
 
{"query":"{ __schema { types { name } } }"}
HTTP/1.1 200 OK
Content-Type: application/json
 
{"errors":[{"message":"GraphQL introspection is not allowed by Apollo Server, but the query contained __schema or __type. To enable introspection, pass introspection: true to ApolloServer in production"}]}

That message (common on Apollo) confirms introspection is off. Now use suggestions.

Step 2: harvest field suggestions

Query a field name that is almost right. A helpful server tells you the real one:

POST /graphql HTTP/1.1
Host: target.example
Content-Type: application/json
 
{"query":"{ user { emial } }"}
HTTP/1.1 200 OK
Content-Type: application/json
 
{"errors":[{"message":"Cannot query field \"emial\" on type \"User\". Did you mean \"email\"?"}]}

The server just confirmed that User has an email field. Each wrong guess that is close to a real name returns the nearest match. This is a graphql-js behavior (the reference JS implementation) that several ports copy, such as Python's graphql-core, so it is common but not universal, and a server can disable it.

Step 3: rebuild the schema by fuzzing

Doing this by hand is slow, so automate the guess-and-collect loop. The canonical tool is Clairvoyance, which fuzzes field names against a wordlist, collects the suggestions, and reconstructs the schema even with introspection disabled:

Clairvoyance        field-fuzzing -> rebuilt schema from "Did you mean" hints
known-schema wordlists   seed the fuzzer with common field/type names
front-end queries        the SPA bundle reveals real field names directly, no guessing

Edge variants

{ user { xxx } }         a far-off name may return no suggestion; try names close to real ones
alternate content-type   application/graphql or a tweaked body can slip past a naive WAF filter
whitespace / aliases     reformatting or aliasing a blocked field name can dodge a crude string block on "__schema"

Naive defenses that block the literal string __schema are easy to walk around with whitespace, aliases, or an alternate content type. Suggestion harvesting sidesteps that entirely, because it never uses __schema at all.

Fixing it

  • Disable field suggestions in production, not just introspection. On JS stacks use a plugin such as graphql-armor's block-field-suggestions.
  • Do not rely on blocking the __schema string. Filters are bypassable. Disable the feature at the engine.
  • Keep front-end queries lean so the bundle does not hand over the full field list.

Full guidance is in preventing GraphQL attacks. Next, turn a discovered argument into injection in unsanitized argument injection, or rebuild a schema on a free lab.

← Back to GraphQL Hacking: The Complete Practical Guide