GraphQL overexposure
Theory
Why This Matters
In 2021, a researcher discovered that the HackerOne GraphQL API returned sensitive internal fields — including undisclosed vulnerability reports — to any authenticated user because field-level authorization checks were absent from several resolvers. GraphQL's expressive query model, which is one of its greatest engineering strengths, becomes a significant attack surface when developers assume that hiding a field from documentation is equivalent to restricting access to it. The 2022 breach of an Australian telecom provider (Optus) exposed 9.8 million customer records via an unauthenticated API endpoint; while not confirmed as GraphQL, the pattern of schema-wide data exposure without per-resolver authorization is identical. OWASP API Security Top 10 lists Broken Function Level Authorization (API5) and Excessive Data Exposure (API3) — both directly applicable to GraphQL overexposure.
Core Concept
GraphQL is a query language for APIs in which the client specifies exactly which fields it wants, and a single endpoint (/graphql) handles all operations. The schema is self-describing: every GraphQL server implements a built-in introspection system that allows clients to query the full type system, available queries, mutations, and field names.
The introspection query (__schema, __type) is enabled by default in most frameworks and intended only for development. When left enabled in production it gives an attacker a complete map of the data model — equivalent to handing out the database schema. Frameworks like Apollo Server disable introspection in production mode by default, but many deployments override this or run in development mode.
Even when introspection is disabled, field enumeration via aliases and fragments can probe for field existence by observing whether queries return null versus a field-not-found error. The deeper issue is resolver-level access control: each field resolver is a function, and if the developer forgets to add an authorization check inside the resolver, any authenticated (or even unauthenticated) client can retrieve that field by naming it in a query.
Batching attacks exploit GraphQL's ability to send multiple operations in a single HTTP request (either as a JSON array or via aliases), enabling brute-force and enumeration that would be rate-limited on a REST API. Circular query DoS (also called query depth attacks) abuses recursive type references to generate exponentially expensive server-side resolution.
Technical Deep-Dive
# Full introspection query — paste into GraphQL Playground or send via curl
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
types {
name
kind
fields {
name
type { name kind ofType { name kind } }
args { name type { name kind } }
}
}
}
}
POST /graphql HTTP/1.1
Host: victim.com
Content-Type: application/json
Authorization: Bearer <low_priv_token>
{"query":"{ __schema { types { name fields { name } } } }"}
# Alias-based field probe when introspection is disabled
# If 'adminNotes' exists, the response key is present; if not, a field error is returned
query ProbeFields {
user(id: 1) {
id
email
a1: adminNotes # probe for hidden admin field
a2: internalRiskScore # probe for another hidden field
}
}
# Batching brute-force — send many mutations in one request to bypass per-request rate limits
[
{"query": "mutation { login(user:"admin", pass:"password1") { token } }"},
{"query": "mutation { login(user:"admin", pass:"password2") { token } }"},
{"query": "mutation { login(user:"admin", pass:"password3") { token } }"}
]
Security Assessment Methodology
- Locate the GraphQL endpoint — common paths are
/graphql,/api/graphql,/v1/graphql. Use feroxbuster with a GraphQL-focused wordlist or look for the endpoint in JavaScript bundles. - Run a full introspection query using InQL (Burp Suite plugin) or GraphQL Voyager. If successful, export the schema and map all types, queries, mutations, and subscriptions.
- Test introspection bypass — if
__schemais blocked, try__schema{queryType{name}}(minimal introspection) or use Clairvoyance tool, which reconstructs the schema via field probing without introspection. - Identify fields lacking authorization — for each type, attempt to query fields that appear sensitive (email, role, password, token, internal*) using a low-privilege token.
- Test batching — send a JSON array of repeated queries to identify rate-limit bypass opportunities.
- Probe query depth limits — construct a deeply nested query (10+ levels) using graphql-threat-matrix payloads to test for missing depth/complexity limits.
Defensive Countermeasure — Disable introspection in production using framework-level settings (e.g.,
introspection: falsein Apollo Server). Implement per-resolver authorization checks — never rely on schema shape alone to hide data. Apply query depth limits (max 5–7 levels), complexity analysis, and per-IP rate limiting on the GraphQL endpoint. Use persisted queries in production to block arbitrary query submission entirely.
Common Assessment Errors
- Stopping when introspection is disabled — introspection off does not mean fields are protected; use Clairvoyance or alias probing to enumerate schema without introspection.
- Testing only GET requests — many GraphQL endpoints only accept POST; some accept both; the security controls may differ between methods.
- Ignoring subscriptions — WebSocket-based GraphQL subscriptions (real-time events) are often overlooked but may expose the same data as queries, without the same authorization middleware applied.
- Missing the batch attack surface — treating GraphQL like REST and testing only one operation per request misses the batching vector for brute-force and enumeration.
- Assuming schema = enforcement — a field absent from the published schema is not necessarily inaccessible; internal or deprecated fields may still be resolvable.
- Overlooking mutation side effects — mutations that update objects may accept extra fields (mass assignment analogue) not visible in the documented schema, escalating privileges.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0007 | Knowledge of authentication, authorization, and access control methods | Covers resolver-level authorization failures and the gap between schema visibility and data access enforcement |
| K0065 | Knowledge of policy-based and rule-based access control | Explains how GraphQL's field-level model requires per-resolver policy rather than URL-path-based rules |
| S0001 | Skill in conducting vulnerability scans and recognizing vulnerabilities in security systems | Practises schema enumeration via InQL/introspection, alias probing, and batching attack construction |
| T0028 | Task: Identify systemic security issues based on the analysis of vulnerability and configuration data | Develops ability to recognise that missing resolver guards are a systemic data-exposure risk across the full type system |
Further Reading
- GraphQL Security Research: Introspection, Batching and More — PortSwigger Web Security Blog
- OWASP API Security Top 10 — 2023 Edition — OWASP Foundation
- GraphQL Threat Matrix — nicktindall, GitHub
- Hacking APIs: Breaking Web Application Programming Interfaces — Corey Ball, No Starch Press (2022)
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.