Todos los posts
Los primeros treinta minutos con una API nueva

Los primeros treinta minutos con una API nueva

El post anterior iba sobre los primeros diez minutos con un bundle JavaScript nuevo. Eso te da un mapa. Este va sobre cómo recorrer el terreno.

El bundle JavaScript te dice lo que el frontend cree que es la API. Flujos de autenticación, qué endpoints llama la UI, qué roles usa el frontend para ocultar cosas. Eso es más o menos el treinta o cincuenta por ciento de lo que el backend expone de verdad. El resto son versiones antiguas que nadie retiró, endpoints internos que la UI no usa, rutas de admin, toggles de debug y parámetros que la documentación nunca menciona porque eran internos.

Treinta minutos es la ventana que le doy a una API nueva antes de decidir si merece más tiempo. El orden de abajo es el que uso. El paso 1 es donde termina el post anterior y de ahí sigues.

Paso 1. Saca lo que ya está visible. Antes de sondear nada, inventaría lo que el propio objetivo te regala. El bundle JavaScript ya te dio los endpoints que llama el frontend. Encima de eso, mira en cada subdominio de API si hay descripciones legibles por máquina que el equipo publicó sin pensarlas como exposición.

# Rutas habituales de OpenAPI y Swagger
for p in /swagger /swagger-ui.html /api-docs /api-docs.json \
         /openapi.json /openapi.yaml /v3/api-docs /docs; do
  curl -so /dev/null -w "%{http_code} $p\n" "https://api.target.com$p"
done

# Y robots.txt y sitemap.xml en cada subdominio
curl -s https://api.target.com/robots.txt
curl -s https://api.target.com/sitemap.xml

# Y GitHub dork para colecciones Postman filtradas
# github.com "collection.json" "target.com"

Cuando cualquiera de estas responde, tienes la superficie entera de la API servida en bandeja. Es el mejor escenario posible. La mayoría de objetivos no filtran spec, pero los suficientes lo hacen como para que treinta segundos de curl sean la apertura correcta.

En el resto del post asumo que no apareció nada. Tienes una lista de endpoints del bundle JavaScript y nada más.

Paso 2. Sondeo de rutas y versiones. Cada /api/v1/X que funciona es una pista de otros tres. Había un /v0/ antes, a menudo todavía desplegado y a menudo anterior al middleware de autenticación actual porque el middleware se añadió después. Probablemente haya un /v2/ en desarrollo al que se llegue con un flip de cabecera o con un acceso directo. Y muchas veces hay un vecino /internal/X, /admin/X, /beta/X o /_private/X en el mismo router.

Sondéalos. Cada versión tiene su propia configuración de auth y rate limit. Los endpoints nuevos sobre versiones actuales se llevan las protecciones modernas. Los endpoints viejos sobre versiones viejas a veces se quedaron con las protecciones que traía el framework en 2019.

# Intercambia el segmento de versión en cada endpoint conocido
for v in v0 v1 v2 v3 internal admin beta _private; do
  curl -so /dev/null -w "%{http_code}  /$v/users\n" \
       "https://api.target.com/$v/users"
done

Una variación que es rara pero vale los pocos caracteres es el cambio de mayúsculas en la ruta. Si /api/getUsers devuelve 403, prueba /API/getUsers, /aPi/getUsers, /api/GetUsers. Algunos middlewares de autorización comparan la ruta respetando las mayúsculas mientras que el router la normaliza sin ellas, de modo que la petición llega al handler con la comprobación de auth saltada. En una década de pruebas lo he visto menos de diez veces, pero es una petición extra por cada 403 y cuando funciona es un bypass de autorización entero. Post completo en el de bypass por mayúsculas.

Paso 3. Enumeración de métodos. La mayoría de gente envía GET. Eso es como un tercio de lo que el backend acepta.

Lanza OPTIONS primero contra cada endpoint descubierto. Está pensado para devolver los métodos permitidos para CORS pero a menudo devuelve más de los que la UI usa. Un endpoint cuya UI solo hace GET pero cuyo OPTIONS anuncia PUT, DELETE, PATCH es un camino de escritura que el frontend no expone. Ahí es donde viven las comprobaciones de auth que faltan, porque el desarrollador que escribió el handler de GET a veces dejó el de escritura conectado sin guardarraíles.

curl -X OPTIONS -i https://api.target.com/api/v1/users/42
# Allow: GET, PUT, DELETE, PATCH, OPTIONS   ← verbos que la UI nunca usa

# Luego prueba cada verbo que la UI no llama
for m in PUT DELETE PATCH POST; do
  curl -so /dev/null -w "%{http_code}  $m /api/v1/users/42\n" \
       -X $m "https://api.target.com/api/v1/users/42"
done

Más allá de GET y OPTIONS, los hits que veo más a menudo: PUT en endpoints que la UI solo lee, DELETE sin confirmación ni re-verificación de auth, PATCH que acepta campos más amplios que los que expone el formulario de edición. La asimetría entre lo que la UI usa y lo que el backend acepta es uno de los sitios más limpios donde cazar bugs en 2026.

Paso 4. Descubrimiento de parámetros. Tienes endpoints. Ahora busca parámetros que la documentación nunca mencionó.

Dos patrones. Primero, parámetros internos habituales que los desarrolladores se dejan olvidados: debug=1, admin=1, expand=, include=, fields=, _debug=, preview=, format=xml, format=csv, raw=1, verbose=1. Échaselos a cada endpoint y fíjate en si alguna respuesta cambia. Un cuerpo más largo, un campo extra, un Content-Type distinto, cualquiera de esas cosas es una señal.

Segundo, contaminación de parámetros. Envía el mismo nombre dos veces con valores distintos.

curl "https://api.target.com/api/v1/items?id=1&id=2"
# El comportamiento varía según framework: Express se queda con el último,
# Flask con el primero, algunos parsers construyen un array. La brecha
# entre lo que elige la capa de auth y lo que elige la capa de datos
# es cómo se cuelan las lecturas entre tenants.

En GraphQL, corre introspection antes que nada. La mayoría de objetivos modernos lo deshabilitan en producción. Los que no te regalan el esquema completo.

curl -X POST https://api.target.com/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{__schema{types{name fields{name}}}}"}'

Paso 5. Sondeo del perímetro de autenticación. La respuesta honesta aquí ha cambiado en los últimos diez años. Los ataques puros de claim swap en JWT están bastante muertos: firma correcta, verificación correcta, re-comprobación en servidor. El alg=none lo parchearon en todas las librerías mainstream hace años. En un objetivo moderno corres las pruebas porque tardan treinta segundos, no porque esperes que entren.

Aun así hay fallos específicos que siguen apareciendo:

  • alg=none. Comprobación de cinco segundos para descartarlo
  • Secretos de firma débiles o por defecto. Pasa el token por jwt_tool --crack con rockyou. Esto me ha entrado un puñado de veces en diez años. El más memorable fue un secreto que resultó ser un único espacio en blanco, " ". Otros han sido cadenas de bootstrap que quedaron puestas y nunca rotaron: secret, changeme, el propio nombre de la empresa, el nombre de pila de un desarrollador. Nada sofisticado. Todo en producción
  • Inyección en el parámetro kid o abuso de jku. Raro, pero es una prueba de cinco minutos
  • Claim swap donde el servidor se fía del campo del JWT sin re-verificar contra la sesión. Todavía pasa en microservicios internos o legacy que delegan la autenticación a un gateway y asumen que arriba ya comprobaron
# Pasada rápida de descarte sobre cada JWT que tengas
jwt_tool <token> -I -pc role -pv admin    # claim swap con alg=none
jwt_tool <token> -C -d /usr/share/wordlists/rockyou.txt

Ninguna de estas es tu primera apuesta en 2026. Son las pruebas que corres mientras mapeas la superficie de auth, así cuando encuentras un endpoint que claramente se fía de un claim a ciegas ya tienes el exploit calentito.

Paso 6. Señal en la respuesta. Las respuestas de la API te dicen más que los cuerpos que devuelven.

  • Rate limits. Pega cien veces a un endpoint conocido en un bucle cerrado. Si ves 429, anota el umbral. Luego prueba lo mismo contra los /internal/ o /admin/ que encontraste en el paso 2. Un endpoint sin rate limit cuando el resto de la API sí lo tiene suele ser una ruta interna que el gateway se olvidó de cubrir
  • Texto de error. Envía cuerpos malformados. Stack traces que nombran el framework, la columna de la base de datos o un hostname interno son señal. DataIntegrityViolationException on user_id column en un error de producción te dice el ORM, el nombre de la columna y que el detalle del error no se está recortando. Las tres cosas alimentan pruebas posteriores
  • Timing. Una lectura normal devuelve en 200ms de forma consistente. Un endpoint devuelve en 2 segundos. Ese está pegando a la base de datos directo, haciendo una llamada HTTP interna o ejecutando una operación cara. Los tres son interesantes
  • Asimetría de códigos de estado. 401 en un endpoint que no existe, 403 en uno que sí. Algunas APIs filtran existencia por código de estado. Eso es un vector de enumeración de usuarios cuando se aplica a IDs de cuenta o de tenant

Si correr seis pasadas sobre cada endpoint te suena pesado, lo es. Hay herramientas que empaquetan el patrón:

  • kiterunner (Assetnote, en Go). Descubrimiento de endpoints con listas de palabras reales de APIs, maneja variación de métodos
  • ffuf con una lista orientada a API, no una lista genérica de fuzzing web. Assetnote publica listas decentes
  • inql para introspection y generación de queries en GraphQL
  • jwt_tool para decodificar, confusión de algoritmo y cracking de secretos débiles en JWT
  • Burp Repeater o httpie para la exploración manual que las herramientas no cubren

Úsalas como primera pasada. Lo que se les escapa es lo que un humano leyendo las respuestas pilla sin esfuerzo: un detalle de mayúsculas, un nombre de parámetro que solo tiene sentido en contexto, un mensaje de error que insinúa el modelo de datos real. La automatización es andamio, no sustituto. Si el escaneo automático vuelve vacío y el objetivo es interesante, léete las respuestas tú.

A los treinta minutos tendrías que tener: una lista de endpoints completa, una idea de qué versiones existen y cómo se autentica cada una, qué métodos acepta el backend de verdad, un puñado de parámetros que la UI no envía, una lectura del modelo de auth y unas cuantas anomalías de timing o rate limit que vale la pena seguir.

Eso no son hallazgos. Es un mapa.

La mayoría de APIs no te sorprende pasados los treinta minutos. La mayoría tienen auth consistente, sin versiones viejas corriendo, sin parámetros de debug olvidados, sin secretos débiles. Vas a gastar más ventanas de treinta minutos que reportes vas a escribir. Tria rápido y pasa de largo sin culpa cuando la API no tiene nada que contar.

Las que sí dan fruto, lo dan normalmente en el paso 2 o en el paso 5 según mi experiencia. Rara vez en el 3 o en el 4. Casi nunca en el 6 por sí solo, aunque el 6 suele confirmar una pista que abrieron los otros.

El bundle JavaScript te dio la idea que tiene el frontend de la API. Los seis pasos de arriba te dan la realidad del backend. La brecha entre esas dos es donde viven los bugs.

¿Quieres que encuentre esto en tu aplicación, o aprender a encontrarlo tú?

Solicitar un pentestReservar mentoría
← AnteriorLos primeros diez minutos con un bundle JavaScript nuevoSiguiente →Encontrando CVEs en WordPress: CVE-2026-39511, inyección SQL en WP Photo Album Plus