Cuatro capas contra un DDoS dirigido al endpoint de login
El problema
El endpoint de login no tenía ninguna defensa. Un ataque DDoS dirigido específicamente contra él — miles de peticiones por segundo — saturó el servidor explotando que cada intento ejecutaba una consulta a BD y un hash bcrypt deliberadamente lento.
El ataque reveló tres problemas a la vez:
- Sin rate limiting — peticiones ilimitadas desde cualquier IP
- Sin bloqueo por intentos — fuerza bruta viable
- Sin segundo factor — una contraseña comprometida equivalía a acceso total
La solución: cuatro capas
Capa 0 — WAF (infraestructura): primera línea antes de que el tráfico llegue a la aplicación. Absorbe el volumen bruto sin consumir recursos de la app.
Capa 1 — Rate limiting: antes de tocar la base de datos, se evalúa el historial de intentos. La decisión clave: los parámetros viven en BD, no en código.
pythondef esta_bloqueado(session, usuario_id: str) -> bool: config = obtener_config(session) # umbrales desde BD, modificables sin redesplegar ventana = now - timedelta(seconds=config.ventana_segundos) intentos = contar_fallidos(session, usuario_id, desde=ventana) if intentos >= config.max_intentos: ultimo = obtener_ultimo_intento(session, usuario_id) bloquear_hasta = ultimo + timedelta(minutes=config.bloqueo_minutos) return now < bloquear_hasta return False
Capa 2 — Registro de intentos: cada intento queda registrado. Cuando el bloqueo expira, los intentos se limpian para no bloquear indefinidamente.
Capa 3 — OTP por email: credenciales correctas no equivalen a acceso. Se genera un código de un solo uso, se invalidan los anteriores, y se envía vía SES de forma asíncrona para no bloquear el endpoint.
pythondef generar_otp(session, usuario_id: str) -> str: # Solo puede haber un código activo — invalidar los anteriores invalidar_codigos_pendientes(session, usuario_id) codigo = f"{random.randint(0, 999999):06d}" guardar_codigo(session, usuario_id, codigo, expira_en=timedelta(minutes=5)) return codigo
El flujo
Petición
│
[WAF] ← bloqueo perimetral
│
[Rate limiter] ← cortar antes de tocar BD
│
[Verificar credenciales]
│
[Generar OTP] → email asíncrono
│
[Validar código: existe + no expirado + no usado]
│
[Emitir token]
Resultado
| Antes | Después |
|---|---|
| Sin defensa perimetral | WAF absorbe el volumen bruto |
| Sin rate limiting | Bloqueo configurable desde BD sin redesplegar |
| Sin auditoría | Registro de cada intento con IP y motivo |
| Contraseña = acceso total | Segundo factor obligatorio |
| Email síncrono | Entrega asíncrona — no bloquea el endpoint |
Lo que aprendí
Los parámetros en BD (umbrales, ventanas, duración del bloqueo) fueron lo que permitió ajustar la defensa en caliente durante el ataque sin tocar el código. El OTP asíncrono también es crítico: si el envío fuera síncrono, el propio ataque habría impedido que los emails llegaran.