the_open_door
coffee
ESEN
← Back to index

GraphQL Abuse

ATT&CKT1190

GraphQL is a query language for APIs: instead of fixed endpoints, it exposes a single endpoint (/graphql) where the client asks for exactly the fields it wants. That flexibility, poorly protected, opens three abuse paths: introspection (discover the whole schema), IDOR (read other users' objects by changing an id), and mutations (modify data without authorization).

Introspection is the equivalent of dumping a database's information_schema: a standard query returns every type, field and relationship in the schema. If the server has it enabled (the norm outside production), it reveals the API's full surface:

{ __schema { types { name fields { name type { name kind } } } } }

With the schema in hand, IDOR is immediate: if a query accepts an id, you iterate it to read other users' data that authorization should block. And mutations let you write: create, update or delete someone else's records if the resolver does not check ownership.

query { user(id: 2) { username email token } }          # IDOR: another user's data
mutation { updatePost(id: 5, body: "pwned") { id body } } # writing over someone else's object

Tools like GraphQL Voyager render the schema returned by introspection so you can grasp it at a glance before building the attack queries.

# fire the introspection with curl (if the GraphiQL IDE is not exposed)
curl -s http://<ip>/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{__schema{types{name fields{name}}}}"}' | jq .
# clairvoyance rebuilds the schema even when introspection is disabled
python3 -m clairvoyance -o schema.json http://<ip>/graphql

Alias batching. One document can repeat a field with different aliases and run them in a single request: useful for brute force (trying many passwords/OTPs while bypassing rate limits, which usually count HTTP requests, not operations):

mutation {
  a: login(user:"admin", pass:"1234"){ token }
  b: login(user:"admin", pass:"12345"){ token }
  c: login(user:"admin", pass:"password"){ token }
}

Requirements. The endpoint must (1) have introspection enabled (or the schema known some other way) and (2) delegate authorization to the client, that is, not check in each resolver that the object belongs to the authenticated user. Without that per-object authorization flaw, knowing the schema is not enough to read or write someone else's data.

How to detect it. In the API log the signature is an IntrospectionQuery (__schema, __type) from a client that is not the application itself, followed by queries iterating the same id field with rising values (the IDOR sweep) or mutations from accounts with no privilege over that object. A spike of /graphql queries with __schema is a direct sign of reconnaissance.

Defence. Disable introspection in production, enforce field- and object-level authorization in every resolver (do not trust that the client only asks for its own), limit query depth and complexity to avoid DoS, and do not expose sensitive fields (tokens, hashes) in the schema. It overlaps with classic IDOR and with API information disclosure.