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 guessingEdge 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
__schemastring. 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.