Ataque: Email bombing 🎯 Límite de peticiones, bloqueo de IPs, validaciones

Tiempo de lectura: 6.49 minutos

Póster del artículo Ataque: Email bombing 🎯 Límite de peticiones, bloqueo de IPs, validaciones

Si tienes un proyecto web y de pronto empiezas a recibir muchos requests para un endpoint específico, entonces puedes estar siendo víctima de un ataque.

¿Cómo se llama dicho ataque?

Depende del objetivo y del modo en que se haga:

  • HTTP flood / DoS (o DDoS si viene de muchos hosts) — si el script manda muchas peticiones para saturar recursos.

  • Abuso de API / scraping — si automatiza llamadas para robar datos o abusar de cuota.

  • Brute-force / credential stuffing — si prueba muchas combinaciones de usuario/clave.

  • Fuzzing / scanning — si envía cargas/inputs aleatorios para encontrar vulnerabilidades.

  • Exploit script — si el script envía payloads para aprovechar una vulnerabilidad concreta (SQLi, RCE, LFI, etc).

A fin de evitar esto, podemos usar/habilitar lo siguiente:

  • WAF (Web Application Firewall).
  • Autenticación / validación de requests.
  • Rate limiting.
  • Captcha en puntos sensibles (login, forms).
  • Bloqueo automático por patrones (fail2ban / Cloudflare rules).
  • Validación y saneamiento para evitar explotaciones (SQLi, XSS).
  • Logs y alertas (requests por IP, user-agent, ratio).

Caso de ejemplo

Hoy vamos a analizar un caso bien particular.

Estaba navegando y de pronto recibí este correo:

Errores en Papertrail

Aquí se advierte que se intentó insertar registros, pero falló, ya que contenían textos muy extensos.

Curioso, ¿no?

Así mismo me encontré con esto en el inbox de [email protected]:

Inbox llena con mensajes de error

Estos mensajes los envía Amazon SES cada vez que falla al enviar un correo.

Es usual ver uno o que otro al momento de enviar campañas de correo.

Pero esta vez, ninguna campaña se encontraba activa y aún así, cada segundo continuaban llegando más y más de estas notificaciones.

Significado: Algo o alguien está provocando que se envíen muchos correos.

En este caso el ataque se conoce como email abuse o email bombing.

También conocido como form abuse.

Investigación general

Como este sitio trabaja con CloudFlare, lo primero que hice fue visitar la sección de Analytics.

Opción Security y Analytics en CloudFlare

Y revisar la cantidad de requests recibidos en las últimas 24 horas.

Desde el siguiente gráfico ya se observa tráfico inusual:

  • En la parte inferior (en el eje X) tenemos las últimas 24 horas.
  • En el eje Y tenemos la cantidad de requests.
Cantidad de requests en las últimas 24 horas

Nota: Puedes abrir la imagen en otra pestaña para verla a tamaño completo.

Como se observa:

  • Cada minuto usualmente tenemos menos de 500 requests.
  • Pero en un momento determinado, hay un pico de 6 mil requests.

Para analizar tráfico inusual es recomendable revisar Sampled logs:

Logs de requests en CloudFlare

Esto muestra ejemplos de requests.

Y desde un primer momento vemos un patrón:

  • Todos los últimos requests provienen de la misma IP.
  • Todos son requests al endpoint `/register/ (la ruta de registro).

A fin de tener una mejor visión global, podemos revisar la sección de Top Statistics:

Estadísticas top de los requests entrantes

Esto nos indica desde dónde proviene la mayor cantidad de visitas.

Por ejemplo:

  • Desde qué IP.
  • Desde qué tipo de dispositivo.
  • Desde qué sistema operativo.
  • Desde qué navegador.

Y en este caso en particular hemos encontrado a la IP ofensiva.

Lo curioso, y bueno de esto, es que el ataque sucedió casi al mismo tiempo en que me conecté a la PC.

En un primer momento los requests se veían de esta manera:

9.26k requests en las últimas 24 horas

Y al poco tiempo, subieron de 9.26k a 19.25k como se observa en la imagen anterior.

Investigación del atacante

En CloudFlare tenemos una opción llamada Security Center, que nos permite indagar más, desde su sección Investigate:

Opción Security Center e Investigate en CloudFlare

Por ejemplo, podemos ingresar la IP del atacante y obtener más información:

Investigando la IP del atacante

Solución

Podemos actuar de muchas formas.

Veamos las que se aplicaron en este caso.

Rate Limiting

Lo primero fue limitar la cantidad de registros (o intentos de registro) que puede hacer cada IP.

¿Por qué alguien intentaría registrar miles de usuarios?

Como es un escenario atípico, este límite no afecta a usuarios legítimos.

A esta práctica de limitar la cantidad de requests permitidos se conoce como rate limiting, cuya traducción literal sería límite de frecuencia o limitación de tasa.

En Laravel esta implementación es muy sencilla:

// Permite hasta 5 requests por hora por IP
RateLimiter::for('register', function (Request $request) {
    return Limit::perHour(5)
        ->by($request->ip())
        ->response(function (Request $request, array $headers) {
            return response()->json([
                'message' => 'Demasiados intentos de registro. Por favor intenta de nuevo más tarde.'
            ], 429, $headers);
        });
});

// Permite hasta 5 requests por minuto por IP
RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)
        ->by($request->ip())
        ->response(function (Request $request, array $headers) {
            return response()->json([
                'message' => 'Demasiados intentos de inicio de sesión. Por favor intenta de nuevo en unos minutos.'
            ], 429, $headers);
        });
});

El código anterior declara límites.

El middleware llamado throttle nos permite aplicar tales límites a nuestras rutas.

Route::post('register', [RegisterController::class, 'store'])
            ->middleware('throttle:register');

Route::post('login', [LoginController::class, 'store'])
            ->middleware('throttle:login');

Security Rules

La práctica anterior detiene el efecto del ataque.

Antes el atacante podía hacer requests continuamente al endpoint de registro, y conseguir que el servidor lo atendiera.

Ahora nuestro proyecto Laravel le respondería con un mensaje de error al exceder el límite.

Sin embargo, aunque los requests ya no tenían alguno, continuaban sucediendo.

Esto significa que hemos validado correctamente a nivel de aplicación, pero aún podemos hacer algo más.

La idea es evitar que la IP ofensiva continúe realizando requests a nuestro servidor.

Para esto nos dirigmos a la sección de Security Rules:

Security Rules en CloudFlare

Y definimos una nueva regla, a fin de excluir dicha IP:

Básicamente definimos lo siguiente:

  • Cuando: la IP es igual a esta.
  • Acción: Bloquear.
Nueva regla para bloquear la ip del atacante

Guardamos cambios y volvemos a revisar nuestros requests:

Requests atendidos por el servidor aún sin requests mitigados

Aquí podemos ver que todos los requests entrantes son atendidos por el servidor.

Pero luego de definir esta regla, vemos que los requests empiezan a ser mitigados por CloudFlare:

Se empezaron a mitigar los primeros requests

Y así luce el gráfico de requests en las últimas 24 horas:

Últimas 24h de requests Últimas 24h de requests ahora con requests mitigados

Empieza a aparecer una línea naranja representando a los requests mitigados.

Significado: Estos requests no son enviados a nuestro servidor, quitándole carga.

El resumen de 24 horas muestra cómo la cantidad de requests mitigados va en aumento:

Últimas 24h 7.54k requests fueron mitigados

Al revisar las últimas 6 horas, confirmamos que el ataque es reciente:

Filtro de requests en las últimas 6 horas Últimas 6h 7.59k requests fueron mitigados

Y así mismo vemos cómo ahora tenemos más requests mitigados que requests atendidos por nuestro servidor.

Más requests mitigados que atendidos 4.56k requests mitigados

En este punto, el servidor sólo atiende requests legítimos.

Objetivo del ataque

Si revisamos un fragmento del error reportado por Papertrail, encontramos lo siguiente:

Oct 18 12:41:57 lamp-pym-nyc3 php: [2025-10-18 12:41:57] production.ERROR: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'name' at row 1 (Connection: mysql, SQL: insert into `users` (`name`, `email`, `password`, `confirmation_code`, `updated_at`, `created_at`) values (✅ We’ve partnered with Binance. Instead of the iPhone 16 Pro Max, you have got an account with 1.3444 BTC - https://***.org/BINANCE-10-17-10?d7zn - After withdrawing the funds, please send proof of the prize to the Binance online support chat ✅, [email protected], ***, 9i0WqiypGouXx540Ic6UghYRW, 2025-10-18 12:41:57, 2025-10-18 12:41:57))

El atacante intentó registrar muchos usuarios con un nombre bien extenso, al punto que producía un error al ejecutar el INSERT en la base de datos.

El mensaje está en inglés. Traducido al español dice que nos hemos asociado con Binance, y que te estamos otorgando una cuenta con 1.3444 BTC.

Lo más probable es que busquen que se visite la página promocionada, con algún engaño o estafa.

Por seguridad he omitido el dominio.

La idea de usar el registro es que, por cada usuario nuevo, nosotros enviamos un correo de confirmación.

Imagina que el correo dice:

Hola Juan, por favor confirma tu correo.

Ahora reemplaza Juan por el texto mencionado previamente.

Ese era el objetivo del atacante.

Como el texto era muy extenso, fallaba. Y de seguro no sabía por qué.

Pero en un momento determinado, lo adaptó para que el sistema lo aceptara.

Una vez frenado el ataque, y comprendido cuál fue el objetivo, ¿qué más podemos hacer?

Analizando el impacto

Primero veamos cuántos usuarios han sido insertados en la base de datos.

select count(1)
from users 
where id >= 25734 and name like '%https://%' ;

Esto nos indica que 1227 "usuarios" fueron creados por el atacante.

De ellos, 4 usuarios confirmaron su registro:

select *
from users 
where id >= 25734 and name like '%https://%' 
and confirmed = 1;

Como ellos no son usuarios legítimos de la plataforma, lo mejor es eliminar sus cuentas:

delete from users 
where id >= 25734 and name like '%https://%';

El hecho de que el delete funcionara demuestra que no han navegado por el sitio.

Además de estos "falsos usuarios" veamos si históricamente se han creado usuarios conteniendo una URL en el nombre:

select * from users where name like '%https://%';

Esto demuestra que ninguno de los usuarios existentes tenía este patrón.

Tampoco será posible de ahora en adelante, gracias a los siguientes ajustes.

Posterior a la solución

Implementemos un par de detalles más, para estar preparados por si se intenta algo similar en el futuro.

Min y Max

Primero, revisemos cuál es la longitud máxima que tiene el correo de nuestros usuarios.

La idea es validar a nivel de aplicación sin siquiera alcanzar la base de datos.

Por ejemplo, ejecutamos la siguiente query:

SELECT MAX(LENGTH(email)) AS max_email_length
FROM users;

El resultado es 53.

Entonces con limitar hasta 60 caracteres el correo me parece razonable.

Tan sólo @gmail.com tiene 10 caracteres.

Por tanto, exigir un mínimo de 12 me parece también justo.

El ataque en este caso fue contra el nombre, pero no quisiera limitar la longitud, porque realmente existen nombres bien largos.

RegEx

Lo que sí podemos hacer es usar una expresión regular.

Como la siguiente:

'regex:/^[\pL\s\-_.]+$/u'

La idea es evitar que se agreguen direcciones URL en la columna name.

Expliación de la regular expression anterior:

Parte Significado
/^ Representa el inicio del string
[...] Hace match con cualquier 1 caracter en su interior
\pL Cualquier letra de cualquier lenguaje (Incluyendo el Español: á, é, í, ó, ú, ñ, etc)
\s Espacios en blanco, tabs (permite dejar espacios entre los nombres)
- Guión (permite "García-López")
_ Guión bajo (permite "John_Smith")
. Punto (permite "Dr. Smith" o iniciales)
+ Uno o más caracteres deben encontrarse de la expresión entre corchetes
$/ Representa el final del string
u Modo Unicode (permite tildes, emojis, etc)

La idea es validar los nombres de nuestros usuarios aplicando esta regEx.

Ejemplos de lo que está permitido ✅

"María García"       // ✅ Tildes
"Jean-Pierre"        // ✅ Guiones
"Dr. Smith"          // ✅ Puntos
"José_Luis"          // ✅ Guiones bajos
"李明"                // ✅ Caracteres chinos
"Владимир"           // ✅ Caracteres cirílicos
"محمد"               // ✅ Caracteres árabes

Ejemplos que no pasan la validación ❌

"John123"            // ❌ Números
"test@email"         // ❌ Símbolos
"<script>"           // ❌ Caracteres especiales / HTML
"user!name"          // ❌ Exclamación
"name#tag"           // ❌ Hashtag
"hola😀"             // ❌ Emojis

Conclusión

Muchas veces pasamos por alto validaciones que asumimos obvias o muy básicas.

Validar es muy sencillo, y aún así a veces nos preguntamos:

¿Por qué validar la longitud mínima y máxima?

Por más simples que parezcan, como hemos visto hoy, reducen significativamente los vectores de ataque.

Si deseas continuar aprendiendo, te invito a visitar mis cursos.

¡Gracias por leer hasta el final!

# laravel # mail # testing # mysql # sql # notifications

Logo de Programación y más

Comparte este post si te fue de ayuda 😉.

Regístrate

Accede a todos los cursos, y mejora tus habilidades 🚀.

Cursos Recomendados 🚀

Imagen para el curso Aprende SQL

Aprende SQL

Aprende SQL y Bases de datos. Curso totalmente práctico. Usa JOINs, subqueries, UNION, y mucho más.

Iniciar curso
Imagen para el curso Docker y Microservicios

Docker y Microservicios

Aprende por qué es importante y cómo funciona Docker, con este nuevo curso práctico!

Iniciar curso
Imagen para el curso Aprende Javascript

Aprende Javascript

Domina JS con este curso práctico y completo! Fundamentos, ejemplos reales, ES6+, POO, Ajax, Webpack, NPM y más.

Iniciar curso

Artículos Relacionados 📚

Espera un momento 🎁 ...

¿Te gustaría aprender a programar, gratis?

Mago de Programación y más

Sólo debes registrarte 😉.