Resumen
Durante la auditoría de una aplicación web con autenticación en dos factores (2FA) basada en códigos OTP enviados por email, se descubrió que el sistema de rate limiting y generación de códigos trataba la dirección de email de forma case-sensitive, mientras que el protocolo SMTP entrega los emails de forma case-insensitive (segun RFC 5321).
Esto significaba que user@target.com, User@target.com, USER@target.com y cualquier otra variación de case generaban códigos OTP independientes con rate limits separados, pero todos llegaban al mismo buzón de correo del atacante o la víctima.
Detalles técnicos
Comportamiento normal
El flujo de autenticación era: login con email + password, y luego un código OTP de 6 dígitos enviado al email. El rate limit era de 3 intentos por código y 1 código cada 60 segundos por email.
POST /api/auth/request-otp HTTP/1.1
Content-Type: application/json
{ "email": "victim@company.com" }
→ 200 OK — Código OTP enviado (rate limit: 1/min para este email)
Bypass mediante variaciónes de case
Cada variación de case se trataba como un email distinto a nivel de aplicación, generando un nuevo código OTP con su propio rate limit:
POST /api/auth/request-otp { "email": "victim@company.com" } → Código A
POST /api/auth/request-otp { "email": "Victim@company.com" } → Código B
POST /api/auth/request-otp { "email": "VICTIM@company.com" } → Código C
POST /api/auth/request-otp { "email": "vIctim@company.com" } → Código D
POST /api/auth/request-otp { "email": "viCtim@company.com" } → Código E
...
Todos los códigos llegan al mismo buzón: victim@company.com
Cada variación tiene su propio rate limit de 3 intentos
Impacto en el brute force
Con un email de 6 caracteres en la parte local, existen 2^6 = 64 variaciónes de case posibles. Cada una genera un código OTP valido con 3 intentos:
Código OTP: 6 dígitos → 1.000.000 combinaciónes posibles
Rate limit normal: 3 intentos / código = 0.0003% probabilidad
Con bypass:
- 64 variaciónes de case × 1 código cada una = 64 códigos válidos
- Cualquier código es valido para la misma cuenta
- 64 códigos × 3 intentos = 192 intentos totales
- Con 64 códigos válidos de 6 dígitos:
P(éxito) = 1 - (999936/1000000)^192 ≈ 1.2%
Repitiendo el proceso cada 60 segundos:
- En 10 minutos: P(éxito) ≈ 11.5%
- En 1 hora: P(éxito) ≈ 52%
Impacto
- Bypass del mecanismo 2FA mediante multiplicación de códigos válidos
- Evasión de rate limiting al generar identidades distintas para el mismo buzón
- Acceso no autorizado a cuentas protegidas con OTP por email
- Flood del buzón de la víctima con múltiples emails de código OTP
Remediación
- Normalizar el email a minúsculas antes de cualquier operación —
email = email.strip().lower() - Rate limiting por email normalizado — Aplicar limites sobre la version canonicalizada del email.
- Limitar códigos OTP activos — Invalidar el código anterior al generar uno nuevo para el mismo buzón.
- Implementar lockout progresivo — Bloquear la cuenta temporalmente tras N intentos fallidos acumulados.
- Considerar TOTP — Migrar a códigos basados en tiempo (Google Authenticator) que no dependen del email.