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.
Modelo de amenaza
Sección titulada «Modelo de amenaza»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.
Red Team
Sección titulada «Red Team»Descubrimiento e introspección
Sección titulada «Descubrimiento e introspección»# ¿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.
Herramientas
Sección titulada «Herramientas»- 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.
Impacto
Sección titulada «Impacto»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.
Blue Team
Sección titulada «Blue Team»Detección
Sección titulada «Detección»- 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.
Telemetría
Sección titulada «Telemetría»Loguea operación, profundidad, coste calculado y usuario por cada query; alerta sobre introspección y batching.
Hardening
Sección titulada «Hardening»- 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.
Respuesta
Sección titulada «Respuesta»Revoca tokens, audita qué campos/objetos se extrajeron, activa límites de coste y desactiva introspección si estaba abierta.
CVEs y casos reales
Sección titulada «CVEs y casos reales»- 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.
Checklist de prueba
Sección titulada «Checklist de prueba»- ¿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?