Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel AvanzadoGratis

Mass PII Extraction vía GraphQL — 93 perfiles reales en 1 hora

Un endpoint GraphQL de sincronización de contactos sin rate limiting, sin verificación de propiedad y con batching de 200 números por petición. Resolución teléfono → identidad real.

Gorka El Bochi9 de mayo de 202613 min

Respuesta rápida

Un endpoint GraphQL de "sincronización de contactos" en una plataforma de IA conversacional aceptaba arrays arbitrarios de teléfonos por petición, sin rate limiting y sin verificar que pertenecieran a la agenda del usuario. PoC: enumeración del prefijo +34 636 (1 millón de candidatos) en 1 hora, 93 perfiles reales extraídos con UID, handle, nombre completo, bio y fotos. Cero detecciones.


1. Contexto — la función de sincronización de contactos

La app móvil de la plataforma incluye una funcionalidad de sincronización: el usuario puede subir los números de su agenda y la plataforma devuelve qué números corresponden a cuentas registradas. Esta funcionalidad está expuesta como un endpoint GraphQL en la API.

El endpoint es:

arduino
POST https://api.target.tld/api/gql_POST
Operation: PoeDeviceContactForPhoneNumbersQuery

El fallo es que este endpoint:

  • No tiene control de acceso más allá de tener una sesión autenticada.
  • No verifica que los números enviados pertenezcan a la agenda del dispositivo.
  • No tiene rate limiting.
  • Acepta arrays de cientos de números por petición.

2. La petición vulnerable

http
POST /api/gql_POST HTTP/2
Host: api.target.tld
Cookie: m-b=...; m-login=1; m-s=...; m-uid=...
Content-Type: application/json

{
  "extensions": {
    "hash": "c56e7237b4ac9258dc9dd8633a4165ba386052e8e0ed964b5c9515003e58139c"
  },
  "operationName": "PoeDeviceContactForPhoneNumbersQuery",
  "variables": {
    "phoneNumbers": ["+34636138342", "+34636180219"]
  }
}

El campo phoneNumbers acepta un array de teléfonos arbitrarios — no tienen que estar en la agenda del dispositivo ni tener relación con el usuario autenticado.


3. La respuesta — PII devuelta por cada match

Por cada número que corresponde a una cuenta registrada, el endpoint devuelve:

json
{
  "data": {
    "poeDeviceContactForPhoneNumbers": [
      {
        "poeUser": {
          "uid": 3140577186,
          "handle": "exampleuser",
          "fullName": "John Doe",
          "bio": "...",
          "profilePhotoURLSmall": "https://...",
          "profilePhotoURLMedium": "https://..."
        },
        "contactPhoneNumber": "+34600000001"
      }
    ]
  }
}
CampoDescripción
uidID único del usuario en la plataforma
handleNombre de usuario público
fullNameNombre completo real
bioBiografía del perfil
profilePhotoURLSmallURL de foto de perfil (baja resolución)
profilePhotoURLMediumURL de foto de perfil (media resolución)
contactPhoneNumberTeléfono confirmado como vinculado a esa cuenta

El resultado es un mapeo directo y confirmado de número de teléfono → identidad real.


4. Características que amplifican el impacto

Sin rate limiting. Durante el PoC se enviaron peticiones de forma continua durante horas contra el prefijo +34 636 (1.000.000 de números candidatos) sin encontrar ningún throttling, bloqueo, CAPTCHA ni retraso progresivo. Cada petición devolvió respuesta con éxito.

Batching masivo. El campo phoneNumbers acepta arrays de hasta 200 números por petición (probado en el PoC). Esto permite enumerar grandes rangos con muy pocas peticiones HTTP.

Sin verificación de propiedad. El endpoint no comprueba que los números enviados existan en la agenda del dispositivo del usuario que hace la petición. Cualquier número arbitrario es válido como input.

Sin relación previa requerida. No hace falta interacción anterior entre el atacante y las víctimas cuyos datos se extraen.


5. El script de automatización

Para demostrar el impacto a escala, se desarrolló un script Python que automatiza la enumeración completa de cualquier prefijo telefónico de cualquier país.

Funcionamiento:

  1. Itera sobre un rango configurable de números (secuencial o aleatorio).
  2. Agrupa los números en batches del tamaño configurado.
  3. Envía cada batch al endpoint GraphQL con las cookies de sesión.
  4. Registra en tiempo real todos los matches encontrados en un CSV.

Parámetros principales:

ParámetroDescripciónDefault
--modesequential (escaneo completo) o random (muestreo)sequential
--prefix-rangeRango de prefijos a enumerar, ej. 636 636
--batchNúmeros de teléfono por petición50
--delaySegundos entre peticiones1.0
--outputNombre del CSV de salidausers_spain.csv
--resumeRetoma desde el último estado guardadofalse

Comando del PoC:

bash
python3 phone_enum.py --mode sequential --prefix-range 636 636 --batch 200

CSV de salida: columnas phone, uid, handle, fullName, bio, profilePhotoURLSmall, profilePhotoURLMedium, timestamp.


6. Resultados reales del PoC

Enumeración contra el prefijo español +34 636 (1.000.000 de candidatos):

  • Tiempo de ejecución: ~1 hora.
  • Números candidatos enviados: 1.000.000.
  • Perfiles de usuario reales extraídos: 93.
  • Rate limiting encontrado: ninguno en ningún momento.
  • Bloqueos o detecciones: ninguno.

El CSV de resultados incluye los 93 perfiles con teléfono, UID, handle, nombre completo, bio y fotos. El perfil del propio investigador aparece en los resultados, confirmando la cobertura correcta del escaneo.

+34 636 es uno de los 190 prefijos disponibles solo en España. El script es directamente aplicable a cualquier país del mundo cambiando el parámetro de prefijo.


7. Impacto observado

  • Extracción de 93 perfiles reales con PII completa en 1 hora de escaneo sobre un único prefijo.
  • Mapeo confirmado teléfono → nombre real, UID, handle, bio y fotos de perfil.
  • Cero mecanismos defensivos activados durante todo el proceso.
  • El mismo script funciona contra cualquier prefijo de cualquier país sin modificaciones.

8. Clasificación técnica

CampoValor
Tipo de vulnerabilidadIDOR + Missing Rate Limiting + Missing Input Validation
CWECWE-359 — Exposure of Private Personal Information
Endpoint afectadoPOST https://api.target.tld/api/gql_POST
Operación GraphQLPoeDeviceContactForPhoneNumbersQuery
Autenticación requeridaSí — cuenta gratuita
Interacción de la víctimaNinguna
Complejidad de explotaciónMuy baja — petición HTTP con array de teléfonos

9. Notas técnicas

  • El endpoint usa un hash fijo para identificar la operación GraphQL: c56e7237b4ac9258dc9dd8633a4165ba386052e8e0ed964b5c9515003e58139c. Este hash es estático y reproducible.
  • La sesión necesaria se obtiene de las cookies de la app móvil capturadas con un proxy (Burp Suite en el PoC).
  • El batch de 200 números por petición fue el tamaño testeado en el PoC — es posible que el límite superior sea mayor.
  • La respuesta solo devuelve datos de números que efectivamente tienen cuenta. Los números sin cuenta no generan error, simplemente no aparecen en el array de respuesta.

Labs relacionados

Practica enumeración de endpoints GraphQL, batching y caza de PII a escala: labs de GraphQL.

Practica esto en un lab

Graphql

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados