Rate Limiting contra Abuso y Ataques de Fuerza Bruta
Rate limiting como control de seguridad contra fuerza bruta, credential stuffing, scraping y DDoS L7: límites por capas, ajuste por endpoint y gestión del 429.
Visión general
El rate limiting se gana su sitio en una pila de seguridad porque casi todo abuso automatizado comparte un rasgo: el volumen. Un ataque de credential stuffing necesita miles de intentos de login. Un scraper necesita cada página, y rápido. Una inundación L7 necesita más peticiones por segundo de las que planificaste. El limitador corta todo esto de raíz haciendo que el volumen salga caro.
Esta receta trata el rate limiting como una capa defensiva, no como una función de shaping de tráfico. Los algoritmos de implementación (token bucket, sliding window, fixed window) se cubren paso a paso en Rate limiting para APIs y Rate limiting con Redis. Lo que sigue es el lado de la seguridad: qué capa bloquea qué, cómo identificar clientes que el atacante no pueda rotar a bajo costo, qué tan estricto debe ser cada endpoint y qué hacer después de que un cliente sobrepase el límite.
Cuándo usarlo
Despliega rate limiting cuando te enfrentas a:
- Ataques de fuerza bruta o credential stuffing contra
/login,/auth/token, restablecimiento de contraseña o endpoints de OTP - Scraping de catálogos, resultados de búsqueda o datos de usuario a velocidad de máquina
- Inundaciones L7 de peticiones que pasan por debajo del radar de la protección DDoS de capa de red
- Abuso de costo, donde un solo cliente quema operaciones caras: búsquedas, exportaciones, llamadas a IA, envío de SMS
- Filtración de API keys o tokens, cuando una credencial robada se está usando intensivamente desde un único origen
No confíes en él cuando:
- El ataque es volumétrico (L3/L4) — eso lo absorben las defensas de red de tu CDN o WAF
- El tráfico viene de un conjunto pequeño de servicios internos confiables que controlas — usa autenticación y cuotas separadas
- El endpoint es legítimamente de alto volumen, como un health check o un receptor de webhooks — un límite genérico generará falsas alarmas
Elegir límites según el tipo de ataque
Cada abuso pide límites y claves distintas:
| Ataque | Qué lo dispara | Límite recomendado | Clave |
|---|---|---|---|
| Login por fuerza bruta | Fallos repetidos en una cuenta | 5 intentos/min | IP + cuenta |
| Credential stuffing | Muchas cuentas desde un origen | 10 intentos/min | IP (+ fingerprint) |
| Abuso de OTP / reset | Volumen de prueba de códigos | 3-5 intentos/min | Cuenta + IP |
| Scraping | Lecturas masivas secuenciales | 60-100 req/min | API key / ID de usuario |
| Inundación L7 | Tasa de peticiones pura | 10-20 req/s de ráfaga | IP |
| Abuso de costo | Llamadas a operaciones caras | 10-30/hora | ID de usuario + endpoint |
Dos reglas prácticas: cuanto más sensible es el endpoint, más estricto y específico debe ser el límite; y la clave debe ser algo que el atacante no pueda rotar barato (las IPs son baratas, las cuentas menos, y los tokens verificados todavía menos).
Solución
Token bucket atómico en Redis (Lua)
La trampa clásica es hacer lectura-modificación-escritura en dos viajes: con concurrencia, las peticiones se intercalan y el limitador deja pasar más de lo debido. Ejecuta la comprobación y el decremento de forma atómica dentro de Redis:
-- token_bucket.lua
-- KEYS[1] = clave del límite (p. ej. "rl:login:1.2.3.4")
-- ARGV[1] = capacidad, ARGV[2] = tasa de recarga (tokens/seg)
-- ARGV[3] = timestamp unix actual, ARGV[4] = TTL de la clave (seg)
local bucket = redis.call('HMGET', KEYS[1], 'tokens', 'last_refill')
local tokens = tonumber(bucket[1]) or tonumber(ARGV[1])
local last_refill = tonumber(bucket[2]) or tonumber(ARGV[3])
tokens = math.min(tonumber(ARGV[1]),
tokens + (tonumber(ARGV[3]) - last_refill) * tonumber(ARGV[2]))
local allowed = 0
if tokens >= 1 then
tokens = tokens - 1
allowed = 1
end
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'last_refill', ARGV[3])
redis.call('EXPIRE', KEYS[1], ARGV[4])
return allowed
import time
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
TOKEN_BUCKET = r.register_script(open('token_bucket.lua').read())
def is_allowed(key: str, capacity: int, refill_rate: float) -> bool:
return bool(TOKEN_BUCKET(keys=[key],
args=[capacity, refill_rate, time.time(), 60]))
# Bloquear fuerza bruta en login: 5 intentos/minuto por IP + cuenta
key = f"rl:login:{client_ip}:{account}"
if not is_allowed(key, capacity=5, refill_rate=5/60):
return Response(status=429, headers={'Retry-After': '12'},
body=b"Rate limit exceeded")
Límites por niveles con rate-limiter-flexible (Express)
La superficie de ataque varía por ruta, así que el limitador también debe variar — estricto en auth, permisivo en lecturas:
const { RateLimiterRedis } = require('rate-limiter-flexible');
const redisClient = require('redis')
.createClient({ url: process.env.REDIS_URL });
// Endpoints de auth: 5 intentos/min, luego bloqueo de 5 min
const authLimiter = new RateLimiterRedis({
storeClient: redisClient, keyPrefix: 'auth',
points: 5, duration: 60, blockDuration: 300,
});
// API general: 100 peticiones/10s
const apiLimiter = new RateLimiterRedis({
storeClient: redisClient, keyPrefix: 'api',
points: 100, duration: 10,
});
function createLimiter(limiter, keyFn = (req) => req.ip) {
return async (req, res, next) => {
try {
await limiter.consume(keyFn(req), 1);
next();
} catch (rej) {
const retryAfter = Math.ceil(rej.msBeforeNext / 1000);
res.set('Retry-After', String(retryAfter));
res.set('X-RateLimit-Remaining', '0');
res.status(429).json({ error: 'Too many requests', retryAfter });
}
};
}
app.use('/api/', createLimiter(apiLimiter));
// Clave IP + credencial: frena que un origen pruebe muchas cuentas
app.use('/api/auth/',
createLimiter(authLimiter, (req) => `${req.ip}:${req.body?.email || ''}`));
Límites en el edge con Nginx
Rechazo barato y temprano, antes de que las peticiones lleguen a tu aplicación:
limit_req_zone $binary_remote_addr zone=auth:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/auth/ {
limit_req zone=auth burst=3 nodelay;
limit_req_status 429;
limit_req_log_level warn; # alimenta tu detección de ataques
proxy_pass http://backend;
}
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
error_page 429 = @ratelimited;
location @ratelimited {
add_header Retry-After "60" always;
return 429 '{"error": "Too many requests", "retryAfter": 60}';
}
}
Verificar que los límites funcionan
Antes de darlo por terminado, golpea un endpoint por encima de su límite y confirma el comportamiento — un limitador mal configurado en silencio no protege nada:
# Ráfaga de 10 peticiones; la cola debería volver con 429
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{http_code} " \
-X POST https://api.example.com/api/auth/login \
-d '{"email":"a@b.c","password":"x"}'
done
# Esperado: 200/401 unas pocas veces, luego 429 con Retry-After
Ajuste por endpoint
Empieza conservador y afloja donde los usuarios legítimos se quejen:
- Endpoints de auth y recuperación — 3-5/min con clave IP + cuenta. Casi nunca ven ráfagas legítimas.
- Lecturas caras (búsquedas, exportaciones, informes) — 10-30/hora por usuario.
- Lecturas normales — 100+/min por usuario o token; el daño por scraping aquí es lento de todos modos.
- Escrituras y mutaciones — 20-50/min; las escrituras masivas dañan más que las lecturas masivas.
Escala la respuesta en vez de bloquear de golpe: primera infracción → 429 con un Retry-After corto; reincidencia → blockDuration más largo; abuso sostenido → manda el origen a tu WAF o a un captcha. Los baneos permanentes de IP son el último recurso — los NAT compartidos y los gateways de operadoras móviles hacen que acabes baneando usuarios inocentes que comparten la dirección.
Lo que funciona
- Devuelve 429 +
Retry-After+ cabecerasX-RateLimit-*para que los clientes legítimos retrocedan sin fricción mientras los atacantes no reciben nada útil. - Usa la identidad como clave, no solo la IP: ID de usuario autenticado, API key o token de sesión. Limitar solo por IP da falsos positivos en NATs compartidos y falsos negativos frente a botnets.
- Alerta sobre picos de 429 en tu stack de monitorización — una ráfaga de rechazos suele ser la primera señal de un ataque en curso, no un bug de cliente.
- Combínalo con WAF y controles de autenticación: el rate limiting frena a los atacantes, pero no los autentica. Úsalo junto a cabeceras de seguridad, autenticación robusta y captcha para reincidentes.
- Fallo cerrado en endpoints de auth: si Redis cae, deniega por defecto en login y reset, y abre el paso solo en lecturas de bajo riesgo.
Errores comunes
- Usar solo la IP detrás de un proxy — tras un balanceador,
req.ipes la dirección del proxy. LeeX-Forwarded-Forcorrectamente (solo el salto confiable) o los atacantes reinician su presupuesto rotando IPs. - Contadores no atómicos — las carreras entre lectura y escritura dejan pasar ráfagas. Usa Lua o una librería que envuelva la comprobación en una sola llamada atómica.
- El mismo límite en todas partes — un techo global o bien ahoga a usuarios legítimos o deja los endpoints de auth expuestos. Los límites deben ajustarse a la sensibilidad de cada endpoint.
- Limitar solo en el edge — un servicio interno comprometido o con bugs se salta el gateway. Mantén también límites en la capa de aplicación.
- Claves que nunca expiran — los timestamps de sliding window y los contadores se acumulan para siempre. Cada clave necesita un TTL.
Lectura adicional
- Documentación del módulo
limit_reqde Nginx - RFC 6585 — Código de estado HTTP 429
- Patrón de rate limiting en Redis
- rate-limiter-flexible (Node.js)
- Bucket4j (Java)
Véase también
- Rate Limiting para APIs — guía de implementación de los algoritmos usados aquí
- Rate Limiting con Redis — coordinación distribuida en profundidad
- Checklist de Seguridad de APIs — dónde encaja el rate limiting en una postura de seguridad completa
Preguntas frecuentes
¿Puede un atacante simplemente rotar IPs para saltarse el rate limiting?
Por eso importa la clave. Los límites por IP fallan frente a botnets y proxies rotativos. Combina IP con identificador de cuenta, fingerprint o token — rotar entonces exige credenciales nuevas, mucho más caras que IPs nuevas.
¿El rate limiting detiene un DDoS?
Ayuda contra inundaciones L7 — peticiones que parecen tráfico real pero llegan demasiado rápido. Los ataques volumétricos L3/L4 necesitan filtrado upstream (CDN, centros de scrubbing) antes de que el tráfico llegue a tus servidores.
¿Qué diferencia hay entre rate limiting, throttling y un WAF?
El rate limiting rechaza peticiones por encima de un umbral (429). El throttling las ralentiza en lugar de rechazarlas. Un WAF inspecciona el contenido — payloads, firmas, patrones. Son complementarios: el WAF mira qué llevan las peticiones y el rate limiting cuántas llegan.
¿Deben tener límites distintos los usuarios autenticados y los anónimos?
Sí. Los autenticados reciben límites por ID de usuario — más justos y más difíciles de burlar. El tráfico anónimo recibe límites por IP con techos más bajos, porque es el grupo de mayor riesgo de abuso.
¿Qué debo registrar cuando se dispara un límite?
La clave que lo disparó, el endpoint, el timestamp y si es reincidente. Alimentar los 429 a tu SIEM convierte al limitador en un sensor de detección — un cliente que sigue chocando contra el muro después de que se le dijo que retrocediera te está diciendo algo.
Recursos Relacionados
Proteger APIs con encabezados de seguridad HTTP
Cómo configurar headers de seguridad esenciales como HSTS, CSP y X-Frame-Options para proteger APIs y aplicaciones web de ataques comunes.
RecipeProteger Formularios Web Contra Ataques CSRF
Cómo prevenir ataques de Cross-Site Request Forgery usando tokens de sincronización, cookies SameSite y patrones de double-submit cookie.
RecipeImplementar Rate Limiting de APIs con Redis
Protege APIs del abuso con limitadores token bucket y ventana deslizante respaldados por Redis, con ráfagas controladas y coordinación distribuida
RecipeRate limiting distribuido con FastAPI y Redis
Implementa rate limiting distribuido en FastAPI usando algoritmos de sliding window y token bucket con Redis para límites por usuario, IP y endpoint.
PatternPatrón Token Bucket: Rate Limiting con Ráfagas Controladas
Cómo implementar rate limiting con token bucket para protección de APIs — refill del bucket, manejo de ráfagas, cuotas por clave y límites distribuidos con Redis.
GuideAPI Security
Asegurar tus APIs end-to-end. Cubre rate limiting, autenticacion, input validation, CORS, prevencion de SQL injection, API gateway patterns, limites de request size, pagination security, mass assignment, versioning, audit logging y API security testing con ejemplos de codigo.