Índice.
Cada vulnerabilidad incluye contexto técnico, por qué los escáneres la pasan por alto, cómo detectarla manualmente y la remediación específica al stack.
Patrones reales extraídos de pentests manuales en plataformas en producción. Por qué las herramientas automáticas las dejan pasar y cómo encontrarlas a mano.
Cada vulnerabilidad incluye contexto técnico, por qué los escáneres la pasan por alto, cómo detectarla manualmente y la remediación específica al stack.
Un escáner automatizado opera bajo dos restricciones técnicas que limitan radicalmente su capacidad de detección: no entiende el dominio del negocio y no construye cadenas de explotación que requieran razonamiento contextual. Estas dos limitaciones son la razón por la que las vulnerabilidades de mayor impacto en plataformas financieras siguen apareciendo, año tras año, en cuentas que ya pasaron por escaneos automáticos sin un solo flag.
El catálogo de un escáner está poblado por firmas: patrones que reconoce y reporta. Una firma puede detectar un parámetro de tipo numérico que acepta una comilla (signo de SQL injection). No puede detectar que un endpoint de transferencia bancaria acepta montos negativos, porque no sabe qué es un monto, ni que debería ser positivo. Esa información vive en la cabeza del desarrollador y en los requirements del producto.
Las vulnerabilidades que documentamos en este whitepaper caen en tres categorías que comparten un denominador: requieren entender qué hace la aplicación, no solo cómo responde.
En proyectos donde los clientes habían contratado previamente escaneos automáticos comerciales, encontramos en promedio 9 hallazgos críticos o altos por engagement que el escáner había marcado como "limpios". El 78% de esos hallazgos eran de las categorías arriba.
La aplicación procesa entradas que técnicamente cumplen el contrato del API (tipos correctos, autenticación válida, parámetros presentes) pero violan reglas implícitas del negocio. El servidor no las rechaza porque la regla nunca fue codificada en el backend; vivía solo en el frontend o en la cabeza del producto manager.
El frontend valida que el campo de monto sea positivo antes de enviar la petición. El backend asume que esa validación ya ocurrió y aplica la fórmula de balance directamente. Cuando un atacante intercepta la petición y modifica el valor a un negativo, el cálculo se invierte y el saldo aumenta en lugar de disminuir.
# Saldo final = Saldo inicial − Monto submitido
# Esperado: 277,943.54 − 24.44 = 277,919.10 ✓
# Inyectado: 277,943.54 − (−24.44) = 277,967.98 ✗
# El operador menos por menos da más.
En engagements reales hemos visto este patrón inflar saldos hasta +$900,000 USD en un solo request en entornos de staging. La operación es trivial: un proxy, un valor con guion. Cinco minutos.
Dos peticiones idénticas enviadas simultáneamente al endpoint de transferencia se procesan ambas porque la verificación de saldo ocurre antes que el lock transaccional. El cliente recibe dos confirmaciones; el saldo solo se descontó una vez.
Burp Turbo Intruder con paquete de 20 peticiones idénticas disparadas en una ventana de 100ms. Si el endpoint procesa más de una transferencia con saldo insuficiente para ambas, la race condition existe.
El escáner no tiene contexto sobre qué representa el campo "amount". No conoce las reglas del negocio (positivo, máximo por transacción, idempotencia). No ejecuta cargas concurrentes diseñadas para explotar race conditions transaccionales.
El cliente envía un campo status en la petición de actualización. El backend lo persiste sin validar si el cambio de estado es legítimo según la máquina de estados del negocio. Un pago marcado como aprobado puede revertirse a pendiente o cancelado mientras el bien o servicio ya fue entregado.
El código de descuento se valida contra la base de datos en cada redención, pero el flag de "ya usado" se persiste solo después de procesar el pedido completo. Una ráfaga de peticiones simultáneas redime el cupón N veces antes de que el flag se actualice.
En una fintech LATAM con 1.2M de cuentas, un endpoint de "aplicar como crédito" aceptaba montos negativos. El equipo de producto había validado el campo en el frontend con un regex; el backend confiaba en esa validación. Detección manual: 12 minutos. Remediación: 1 línea de código (server-side guard).
Broken Object Level Authorization es la vulnerabilidad más común en APIs modernas y la primera categoría del OWASP API Security Top 10 desde 2019. Es también la más fácilmente explotable y la que más impacto financiero produce en plataformas que manejan recursos de usuario (préstamos, transacciones, archivos, registros médicos).
El servidor recibe un identificador del recurso solicitado en el path o el body de la petición. Procede a recuperar el recurso de la base de datos y devolverlo sin verificar primero si el usuario autenticado tiene derecho a verlo. La autenticación está, la autorización no.
# Backend vulnerable
@app.get("/api/loans/{loan_id}")
def get_loan(loan_id: str, user = auth_required()):
return db.loans.find(loan_id) # ← sin filtro de owner
El atacante autenticado con su propio token sustituye el loan_id en el path por uno que infiere o enumera. El servidor responde con el recurso completo: monto, historial de pagos, datos personales del titular, scoring crediticio.
/bulk/delete?ids=[1,2,3,4] aplica filtro de ownership solo al primer ID. Los siguientes se procesan sin validación.El método es directo y exige paciencia disciplinada. Para cada endpoint del API que recibe un identificador de recurso:
id del recurso de la víctima.Este proceso debe repetirse para cada endpoint con identificadores. En APIs de 50+ endpoints, son varias horas de trabajo metódico. Burp Suite con extensiones como AuthMatrix o Autorize automatiza la captura y comparación de respuestas, pero la decisión final de qué endpoint probar y con qué payload sigue siendo manual.
user_id del token, no aceptar el ID del cliente como única referencia.Plataforma SaaS multi-tenant B2B: el endpoint /api/companies/{slug}/employees validaba el slug del path pero no contra el JWT. Cualquier usuario con cuenta válida en la plataforma podía iterar slugs de empresas competidoras y descargar la nómina completa. 4 horas de detección manual; cero hallazgos en el escaneo automático contratado meses antes.
La aplicación firma sus tokens JWT con un algoritmo simétrico (HS256) y, por descuido en el bundling del frontend, el secreto de firma queda embebido en un archivo JavaScript público accesible desde cualquier navegador. Una vez que el secreto está expuesto, un atacante puede generar tokens JWT válidos para cualquier usuario, incluyendo administradores.
El proceso típico empieza descargando el bundle JavaScript principal de la aplicación. Herramientas como jsluice, SecretFinder o regex grep contra patrones tipo SECRET_KEY, SIGNING_KEY, JWT_SECRET producen el match. En engagements reales hemos encontrado el secreto literalmente en una constante exportada:
// main.f2924b6f.js (línea 1247)
const REACT_APP_SECRET_KEY = "[REDACTED-32-char-hex]";
const REACT_APP_AES_KEY = "[REDACTED]";
Las dos constantes salen del archivo .env del proyecto y son inyectadas en el build a través de variables de entorno con prefijo REACT_APP_*. El desarrollador asumió que las variables de entorno son privadas; el bundler de Create React App las expone públicamente por diseño cuando llevan ese prefijo.
Un script en Python decodifica un JWT propio del atacante, modifica el claim user_id al ID de un administrador conocido, y re-firma el token con el secreto exfiltrado. El servidor valida la firma criptográficamente, encuentra que es correcta, y emite la sesión privilegiada.
# Forge mínimo en Python
import jwt
original = "eyJhbGciOiJIUzI1NiIs..."
payload = jwt.decode(original, options={"verify_signature": False})
payload["user_id"] = 1 # admin
forged = jwt.encode(payload, SECRET, algorithm="HS256")
# forged ahora es un token válido para user_id=1
Tener un token administrativo válido no es la explotación final; es el habilitador. Con el token forged, el atacante ejecuta acciones que el endpoint /api/admin/* permite. En el caso documentado, eso incluyó:
El registro del nuevo administrador se ve idéntico a un onboarding normal en los logs, dificultando la detección post-incidente.
trufflehog, gitleaks, o reglas custom de regex en CI/CD. Cualquier match en la rama main bloquea el merge.Un DAST genérico no descarga ni inspecciona el bundle de JavaScript con regex de secretos. Un SAST de aplicación no analiza el bundle empaquetado, solo el código fuente, donde el secreto vive en un archivo .env que está en gitignore. La cadena completa (lectura → forge → uso administrativo) requiere tres pasos manuales que ninguna herramienta encadena por sí sola.
El control más efectivo contra esta clase de vulnerabilidad es la detección automática en el pipeline antes de que el código llegue a producción. Una regla de pre-commit hook combinada con un scan de secretos en el pull request elimina la mayoría de los casos.
# Pre-commit hook con trufflehog
trufflehog filesystem ./src \
--exclude-paths .trufflehog-ignore \
--fail \
--json | jq '.SourceMetadata.Data.Filesystem.file'
Este patrón ataca el problema en el origen: el secreto nunca llega al bundle público porque el commit que lo introdujo se rechaza antes del merge. Combinado con escaneo periódico del frontend en producción (en caso de que un secreto se cuele por otra vía), proporciona defensa en profundidad.
En auditorías a 47 plataformas en producción durante 2024 y 2025, encontramos secretos accionables embebidos en frontend en el 23% de los casos. De esos, el 40% eran claves de firma JWT o secretos de cifrado simétrico que permitían escalación de privilegios directa. La media de tiempo de descubrimiento fue de 18 minutos por proyecto, prácticamente la primera hora de cualquier engagement.
Patrones a buscar en el bundle de JavaScript de producción: SECRET, SIGNING, PRIVATE_KEY, JWT_SECRET, API_KEY, AWS_ACCESS_KEY, STRIPE_SECRET_KEY, FIREBASE_*, SUPABASE_SERVICE_ROLE_KEY. Cualquier match en código compilado debe asumirse como compromiso hasta probarse lo contrario.
El servidor que despacha correos de login con magic link construye la URL del link concatenando el header Origin de la petición HTTP entrante con el token. Sin validación de que el origin pertenece a un dominio confiable, un atacante puede inyectar un dominio bajo su control y recibir el token de autenticación cuando la víctima haga click.
Origin.POST /api/auth/send-magic-link HTTP/1.1
Host: api.app-redacted.xyz
Origin: https://[ATTACKER-COLLABORATOR].oastify.com
Content-Type: application/json
{ "email": "[email protected]" }
From: [email protected]
Subject: Click here to login
Click the following link to access your account:
https://[ATTACKER-COLLABORATOR].oastify.com/?u=yFDJ...&t=[ONE-TIME-TOKEN]
En un phishing convencional el atacante necesita comprar un dominio parecido al legítimo y diseñar una página falsa que copie el login. La víctima entrenada para inspeccionar URLs puede detectar la diferencia. Aquí no aplica: el correo proviene del dominio legítimo, con el remitente real, y el único elemento sospechoso es la URL del link, que la mayoría de usuarios no inspecciona antes de hacer click.
Adicionalmente, el atacante nunca necesita acceso al inbox de la víctima. El token de autenticación se transmite por la propia acción de hacer click. La víctima cree que está iniciando sesión normalmente; el atacante ya tiene la sesión.
Origin no coincide con ninguno.APP_URL), no del cliente. Eliminar la dependencia del header elimina la clase de vulnerabilidad.Cualquier endpoint que envíe correos con links generados a partir de input del cliente: recuperación de contraseña, invitaciones a colaborar, confirmación de email, links de unsubscribe, links de share, links de aprobación. Todos deben usar URL base de configuración del servidor.
El proceso para detectar esta vulnerabilidad es mecánico y rápido. Para cada endpoint que dispara un correo:
Origin a un dominio bajo control del tester (Collaborator funciona sin infraestructura).El test completo toma menos de cinco minutos por endpoint. Una API con cinco endpoints que envían correos puede auditarse en quince minutos. La probabilidad de encontrar al menos uno vulnerable en una primera auditoría es estadísticamente alta: en nuestros datos, aproximadamente el 30% de los servicios web evaluados tenía al menos un endpoint con esta clase de problema.
El mismo patrón aplica a otros headers HTTP que el cliente puede manipular y que el servidor incorpora en respuestas o emails sin validar:
Host para construir URLs absolutas en respuestas o templates de correo.El flujo transaccional valida el código OTP en una petición intermedia, pero la petición final que ejecuta la operación crítica no requiere ni el código ni un identificador de transacción que confirme la validación previa. El frontend siempre envía las dos peticiones en secuencia, así que el desarrollador asume que llegar al paso final implica haber pasado el OTP. Un proxy demuestra que esa asunción es falsa.
{ "valid": true }.Si la petición de confirm no incluye el OTP ni un token de validación vinculado, el atacante puede ejecutar el paso 3 directamente, omitiendo el paso 2. La validación del OTP pasa a ser opcional.
# Con Burp interceptando todas las peticiones de la app
1. Iniciar el flujo normalmente.
2. Ingresar cualquier valor en el campo OTP (incluso vacío).
3. Drop la petición /validate-otp en Burp.
4. Forward la petición /confirm sin modificar.
5. Verificar respuesta. Si retorna 200, OTP era cosmético.
El servidor debe emitir un token de un solo uso al validar el OTP, vinculado al usuario y a la operación específica. Solo peticiones que incluyan ese token pueden ejecutar la operación final. El token tiene expiración corta y se elimina al primer uso.
# Backend · paso 2: validación de OTP
def validate_otp(person_uid, otp_code, operation_id):
if not otp_matches(person_uid, otp_code):
return error("OTP inválido")
confirmation_token = generate_uuid()
redis.setex(
f"otp_confirmed:{confirmation_token}",
300, # 5 minutos
json.dumps({
"person_uid": person_uid,
"operation_id": operation_id,
"validated_at": now()
})
)
return {"confirmation_token": confirmation_token}
# Backend · paso 3: ejecución
def execute_operation(person_uid, confirmation_token, op_data):
confirmation = redis.get(f"otp_confirmed:{confirmation_token}")
if not confirmation:
return error("Token inválido o expirado")
data = json.loads(confirmation)
if data["person_uid"] != person_uid:
return error("Token no corresponde al usuario")
if data["operation_id"] != op_data["operation_id"]:
return error("Token no corresponde a la operación")
redis.delete(f"otp_confirmed:{confirmation_token}") # un solo uso
process_operation(op_data)
Tres propiedades deben mantenerse: vinculación al usuario (un token de la víctima no puede usarse en sesión del atacante), vinculación a la operación específica (el token de una transferencia no puede confirmar otra) y uso único (el token se elimina tras la primera redención).
El escáner no entiende qué es OTP, ni cuál es el flujo correcto, ni qué petición es "intermedia" vs "final". Reproduce el flujo completo según el documento OpenAPI; no prueba combinaciones donde se omiten pasos. Detectar la vulnerabilidad requiere razonar sobre la lógica de la aplicación.
En auditorías a aplicaciones móviles fintech con 2FA implementado, encontramos al menos un bypass del segundo factor en aproximadamente el 35% de los engagements. La variante más común es la falta de vinculación entre validación del OTP y la operación final; representa cerca del 60% de los casos detectados.
Dos vulnerabilidades hermanas: Mass Assignment ocurre cuando el backend deserializa un body JSON directamente sobre un modelo de la base de datos, permitiendo al cliente sobrescribir campos que no deberían ser editables (rol, balance, flags administrativos). Excessive Data Exposure es el espejo: el backend serializa todo el modelo en la respuesta, exponiendo campos internos al cliente.
El endpoint PUT /api/users/profile acepta el body que el cliente envía y lo aplica al usuario autenticado. El frontend solo permite editar nombre, email y avatar. El backend, sin allowlist de campos, acepta cualquier propiedad incluyendo role: "admin" o credit_balance: 999999.
PUT /api/users/profile HTTP/1.1
Authorization: Bearer [valid-user-token]
Content-Type: application/json
{
"name": "Usuario Normal",
"email": "[email protected]",
"role": "admin",
"is_verified": true,
"credit_balance": 999999
}
Si el endpoint responde 200 y los campos inyectados se persistieron, la vulnerabilidad existe. La detección manual exige conocer qué campos puede tener el modelo, lo cual se obtiene leyendo el código fuente, inspeccionando otros endpoints que devuelven el objeto completo, o experimentando con nombres comunes (role, admin, verified, tier, plan).
El espejo: el endpoint GET /api/users/{id} devuelve el documento del usuario completo, incluyendo hash de password, tokens de sesión, internal IDs, flags de feature toggles, datos de billing y otra información que el frontend nunca usa pero que el ORM serializa por defecto.
El frontend ignora los campos extras al renderizar; un atacante haciendo curl al mismo endpoint los recibe en la respuesta JSON. Ataques que se construyen sobre esto incluyen reuso de tokens en otras sesiones, enumeración de roles internos, identificación de cuentas de alto valor para targeted attacks.
password_hash, internal_*, etc.) ante cualquier input. Falla CI si alguno aparece.La causa raíz de ambas vulnerabilidades es el mismo problema de diseño: frameworks que privilegian conveniencia de desarrollo sobre seguridad por defecto. ORM que serializa todo el modelo, deserializadores que aplican el body completo. La fix arquitectónica correcta es invertir el default: nada se acepta o se devuelve a menos que esté explícitamente listado.
Server-Side Request Forgery es la vulnerabilidad que permite al atacante hacer que el servidor ejecute peticiones HTTP a destinos elegidos por el atacante. En arquitecturas cloud modernas (AWS, GCP, Azure) el impacto se amplifica: el servidor tiene acceso a endpoints internos como el metadata service que expone credenciales temporales del rol IAM asociado a la instancia.
Cualquier endpoint que acepte una URL como input y haga una petición desde el servidor: webhooks, importación de imágenes desde URL, parsers de RSS, validadores de OG metadata, generadores de previews de links, integraciones de SSO basadas en metadata XML.
POST /api/import/image-from-url HTTP/1.1
Content-Type: application/json
{ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
El servidor hace fetch a ese URL desde su propia red (donde el metadata service es accesible) y devuelve el contenido en la respuesta. El atacante recibe el listado de roles IAM. Una segunda petición al endpoint específico del rol devuelve AccessKeyId, SecretAccessKey y Token temporales.
Las defensas naive son fáciles de bypassear:
169.254.*, 10.*, 192.168.*): se bypassea con DNS rebinding (un dominio que resuelve primero a una IP pública y al segundo intento a una interna), encoding decimal de IPs (2852039166), notación octal o hexadecimal.http://[email protected] es interpretado por algunos como host allowed.com, por otros como evil.com.Host apropiado.169.254.169.254 es vector clásico de SSRF.Para cada endpoint que acepta URL como input:
Una sola interacción exitosa con Collaborator confirma SSRF. La extensión de impacto depende del entorno cloud y los permisos del rol IAM, que requieren enumeración adicional.
Las siete vulnerabilidades documentadas comparten una estructura: existe una verificación implícita que el desarrollador asume está ocurriendo en otro lugar de la cadena. El frontend asume que el backend valida; el backend asume que el firewall filtra; el firewall asume que la red es confiable. Cuando el atacante rompe esa cadena de asunciones con un proxy, las defensas se desvanecen porque nunca estuvieron en el lugar donde el desarrollador creía.
Un escáner automatizado opera dentro de las mismas asunciones del desarrollador. Si la regla de validación no está codificada explícitamente en un patrón que la herramienta reconoce, no se reporta. La herramienta se vuelve cómplice involuntario del problema.
El pentester con criterio asume que las defensas están donde el código las pone, no donde el desarrollador cree que están. Cada llamada a la API se trata como un input independiente, sin asumir que peticiones previas validaron nada. Cada respuesta se inspecciona buscando datos extras que no debería contener. Cada cambio de estado se cuestiona: ¿por qué el servidor permitió esto, y qué pasaría si lo intento dos veces?
Esta postura mental es la diferencia entre un escaneo automatizado y una auditoría manual. No es un truco; es una rutina disciplinada de cuestionamiento que se construye con años de experiencia ofensiva. Los hallazgos en este documento son ejemplos representativos del tipo de patrón que aparece cuando se ejecuta esa rutina con rigor.
owasp.org/API-SecurityBurp Suite Professional · Proxy de interceptación, Intruder, Repeater, Turbo Intruderjsluice · Extracción de endpoints y secretos en JavaScripttrufflehog, gitleaks · Detección de secretos en código y artefactosBurp Collaborator, oastify · Listeners para SSRF y exfiltraciónjwt_tool · Forge y testing de tokens JWTarjun, paramspider · Discovery de parámetros ocultosEste whitepaper es una destilación parcial de patrones recurrentes observados durante engagements reales en plataformas de producción a lo largo de más de ocho años de práctica ofensiva. La metodología completa se aplica en cada proyecto e incluye fases de reconocimiento, mapeo de superficie, descubrimiento, explotación y reporteo descritas en detalle en la propuesta técnica de cada engagement.
Conversación de 20 minutos con el equipo técnico para entender el alcance, la arquitectura, los flujos críticos y las restricciones (sistemas en producción que no admiten ciertos tipos de prueba). Salida: alcance escrito, fechas firmes, reglas de compromiso.
Catálogo de la superficie de ataque: endpoints del API, archivos JavaScript del frontend, subdominios, servicios expuestos a internet. Identificación de patrones de autenticación, sesiones, validación de input. Salida: documento de superficie con cobertura completa.
Auditoría endpoint por endpoint con la metodología descrita en este whitepaper como base mínima. Cada hallazgo se documenta con prueba de concepto reproducible (request HTTP completo, captura de pantalla, video cuando aplica). Comunicación diaria por Slack o Teams con el equipo técnico del cliente.
Reporte ejecutivo (1-2 páginas) para dirección y reporte técnico detallado para el equipo de desarrollo. Cada hallazgo incluye severidad CVSS, impacto de negocio, prueba de concepto reproducible y guía de remediación específica al stack del cliente. Sesión de walkthrough en vivo con el equipo técnico.
Cuando el equipo del cliente termina las remediaciones, validamos cada hallazgo cerrado y emitimos un addendum al reporte. Sin costo adicional dentro de los primeros 90 días.
Lead pentester · superficie web y APIs REST/GraphQL · OSWE, OSCP, eCPPTv2. Operadores especializados · mobile (iOS/Android, eMAPT, CMPen) · AppSec y AI/ML (CompTIA Security+, C-AI/MLPen) · Web avanzado (eWPTXv2, Burp Suite Certified Practitioner).
Cuéntanos sobre tu API, app móvil o plataforma web y te enviamos una propuesta con alcance y precio fijo en menos de 24 horas.