Saltearse al contenido

GraphQL

GraphQL expone un único endpoint (/graphql) donde el cliente decide exactamente qué datos pide. Esa flexibilidad es también la superficie: el esquema es autodescriptivo (introspección), un solo query puede pedir datos anidados en profundidad, y la autorización —que en REST se reparte por endpoint— aquí hay que aplicarla campo a campo. Cuando no se hace, un resolver sin check filtra lo que quieras.

El error de base es tratar GraphQL como si la capa de transporte autorizara. No lo hace: cada campo y cada mutación es una puerta, y si el resolver no comprueba permisos, da igual que el front no muestre el botón. Añade introspección (el atacante conoce todo el modelo), batching (bruteforce en una petición) y queries anidadas (DoS por amplificación) y tienes una superficie muy rica.

# ¿está vivo el introspection?
curl -s https://target/graphql -H 'Content-Type: application/json' \
-d '{"query":"{__schema{types{name}}}"}'

Query de introspección completa → todos los tipos, campos, argumentos y mutaciones. Si está deshabilitada:

  • Field suggestion: GraphQL sugiere nombres parecidos al equivocarte (Did you mean "user"?) → clairvoyance reconstruye el esquema a ciegas.
  • Endpoints comunes: /graphql, /graphiql, /v1/graphql, /api/graphql, /query.
  • IDOR / BOLA por campo: pide objetos por ID ajeno:
{ user(id: 1337) { email passwordHash privateNotes } }
  • Autorización por mutación (BFLA): ejecuta mutaciones administrativas sin rol:
mutation { deleteUser(id: 1) { ok } updateRole(user: 2, role: "ADMIN") { ok } }
  • Batching para bruteforce/rate-limit bypass: muchas operaciones en una sola petición → salta el rate-limit por request:
[{"query":"mutation{login(u:\"a\",p:\"1\"){token}}"},
{"query":"mutation{login(u:\"a\",p:\"2\"){token}}"}, ...]

o aliases en un único documento:

mutation { a:login(u:"x",p:"1"){token} b:login(u:"x",p:"2"){token} ... }
  • Inyección en resolvers: los argumentos llegan a SQL/NoSQL/comandos igual que en REST → { search(q: "' OR 1=1-- -") }.
  • DoS por anidamiento / amplificación: relaciones circulares profundas (posts{author{posts{author{...}}}}) explotan el backend si no hay límite de profundidad/coste.
  • InQL (Burp), GraphQL Voyager — visualizar/explorar esquema.
  • clairvoyance — reconstruir esquema sin introspección.
  • graphql-cop, graphw00f — auditoría y fingerprint del motor.
  • Burp Repeater con Content-Type: application/json.

Fuga masiva de datos por campos sin autorizar, acciones administrativas por mutaciones sin control, bypass de rate-limit vía batching, DoS por queries anidadas, inyecciones heredadas en resolvers.

  • Queries de introspección (__schema, __type) en producción.
  • Peticiones con cientos de aliases o arrays de operaciones (batching abusivo).
  • Profundidad de anidamiento anómala; errores de sugerencia de campo en masa.

Loguea operación, profundidad, coste calculado y usuario por cada query; alerta sobre introspección y batching.

  • Autorización a nivel de campo/resolver, no de endpoint; no confíes en que el cliente no pida un campo.
  • Deshabilita introspección y field suggestions en producción.
  • Límite de profundidad y de coste (query cost analysis); límite de aliases y de operaciones por batch.
  • Rate-limit que cuente operaciones, no solo peticiones HTTP.
  • Persisted queries (allowlist de operaciones permitidas) para APIs internas.
  • Valida y parametriza los argumentos como cualquier input no confiable.

Revoca tokens, audita qué campos/objetos se extrajeron, activa límites de coste y desactiva introspección si estaba abierta.

  • HackerOne / GitLab / Shopify — múltiples reportes públicos de BOLA/BFLA por falta de autorización a nivel de campo en GraphQL.
  • CVE-2023-xxxx (Hasura / Apollo) — bugs de introspección y de control de acceso en motores populares.
  • Facebook / Instagram — históricos de fuga de datos por resolvers sin check de permisos.
  • Informes de PortSwigger y de la GraphQL Security community documentan batching brute-force y DoS por anidamiento.
  • ¿La introspección está activa en producción?
  • Si no, ¿reconstruible con field suggestions / clairvoyance?
  • ¿Se autorizan los campos/objetos por ID ajeno? (BOLA)
  • ¿Las mutaciones administrativas comprueban rol? (BFLA)
  • ¿El batching/aliases permite saltar el rate-limit?
  • ¿Hay límite de profundidad y de coste de query? (DoS)
  • ¿Los argumentos de resolver llegan a SQL/NoSQL/comandos sin sanitizar?
  • ¿Se filtran stack traces o datos internos en los errores?