ReDoS: cómo una regex puede tumbar tu servidor

Resumen

Una expresión regular mal escrita puede hacer mucho más que devolver datos equivocados: puede congelar un servidor entero. Ese peligro se llama ReDoS (Regular Expression Denial of Service) y aquí vas a entender cómo ocurre y cómo blindar tu código en producción. Esto es clave para cualquier desarrollador que ejecute regex en cada petición web.

Qué es ReDoS y por qué puede tirar tu servidor

ReDoS es un ataque donde alguien envía un texto malicioso diseñado para que tu regex entre en un estado de backtracking catastrófico. Cuando el motor cae en esta trampa, el tiempo de procesamiento deja de ser lineal y crece de forma exponencial respecto al tamaño del input.

Para dimensionar la gravedad, mira cómo escala el problema con un patrón vulnerable:

  • Un input de 30 caracteres malicioso puede tardar un par de segundos.
  • Uno de 40 caracteres puede tardar minutos.
  • Uno de 50 caracteres puede congelar el procesador durante días.

Si tu servidor corre esa regex en cada petición, un solo usuario malintencionado puede consumir el 100% de los núcleos de tu CPU y tirar la plataforma completa [00:44].

¿Qué es ReDoS? Es un ataque de denegación de servicio que aprovecha regex mal escritas. Un texto especialmente diseñado obliga al motor a probar millones de combinaciones, disparando la CPU al 100% y bloqueando el servidor.

Qué casos reales demuestran el peligro

Esto no es teoría de pizarrón, ha tumbado a gigantes de la industria [01:03].

En 2019, Cloudflare tenía una sola regex mal optimizada en su web application firewall. Durante una actualización de configuración entró en backtracking catastrófico y provocó una caída del servicio para una parte masiva de Internet durante casi 30 minutos [01:12].

En 2016, Stack Overflow usaba una expresión regular para recortar espacios en blanco al final de los textos. Un post con un formato particular colapsó el sistema y causó un downtime inmediato [01:29].

Por qué el backtracking catastrófico dispara la CPU

La raíz del problema aparece cuando existen múltiples caminos válidos para emparejar el mismo texto. El motor se ve obligado a probar todas las combinaciones posibles antes de admitir que la cadena falló [01:44].

Piensa en un patrón clásico evaluando el texto aaaa. El motor puede agruparlo de varias formas:

  • Como un solo bloque.
  • Dividido en dos grupos.
  • Separado en tres grupos.
  • Procesando cada letra por separado.

Con un texto legítimo el match es rápido. El verdadero problema aparece cuando alguien inyecta 22 letras a seguidas de un signo de exclamación. Ese signo final obliga a la regex a fallar, pero antes de arrojar el resultado negativo el motor retrocede y prueba millones de caminos posibles. Ahí la CPU se dispara al 100% [02:31].

¿Por qué una regex se vuelve exponencial? Porque hay cuantificadores anidados que crean muchos caminos para emparejar el mismo texto. Cuando el input fuerza un fallo al final, el motor prueba todas las combinaciones antes de rendirse.

Cómo aplicar la regla de oro de Jeffrey Friedl

Existe una regla de oro descrita por Jeffrey Friedl: cuanto más estricto y específico sea tu patrón, menos caminos alternativos tendrá que evaluar el motor, reduciendo el backtracking a cero [02:52].

Con esa idea en mente, protege tu código en producción con estas tres heurísticas prácticas:

  1. Ancla tus expresiones regulares siempre que puedas. Usar el ancla de inicio y de fin permite descartar el match de inmediato si los extremos no cumplen el formato, deteniendo el proceso antes de evaluar el centro.
  2. Evita el punto asterisco si una clase de caracteres acotada puede hacer mejor el trabajo.
  3. Que las alternativas dentro de un grupo no se superpongan. Un patrón donde el punto y el espacio en blanco compiten por el mismo carácter es una bomba de tiempo [03:33].

Siguiendo estas reglas, reduces drásticamente los caminos que el motor necesita explorar.

Cómo detectar y prevenir ReDoS en producción

En entornos como Regex 101 tienes un contador de pasos dentro del debugger. Si notas que tu patrón requiere miles de pasos para procesar una cadena corta, ahí está tu alerta roja de rendimiento [03:56].

Y aquí viene lo interesante: si desarrollas una aplicación donde los usuarios finales escriben sus propias expresiones regulares, como un motor de búsqueda avanzado o filtros dinámicos, no puedes confiar en que estén bien escritas. La única defensa real es cambiar el motor estándar por uno que, por diseño, no haga backtracking.

Los tres estándares de la industria para esto son:

  • RE2, desarrollado por Google para C++, Go y Python.
  • El crate regex de Rust.
  • Hyperscan, desarrollado por Intel.

Estos motores procesan las cadenas en tiempo estrictamente lineal respecto al tamaño del input, lo que los hace inmunes a ataques ReDoS [04:41].

¿Cuándo debo usar RE2 en vez del motor estándar? Si la regex viene del usuario, usa RE2. Si la escribiste tú en tu código, el motor estándar de tu lenguaje está bien, siempre que audites tus patrones.

Qué limitaciones tienen los motores sin backtracking

La única contrapartida es que, al no hacer backtracking, estos motores no soportan características avanzadas como el backreference ni los lookaround [05:00].

Antes de mandar cualquier expresión regular a producción, tómate dos minutos para auditarla en Regex 101. Revisa el contador de pasos, sospecha de los cuantificadores anidados y recuerda que una buena regex no es solo la que encuentra el texto, sino la que mantiene a salvo tu servidor [05:20].

La regla que viaja contigo a cualquier lenguaje es simple: sé tan específico como puedas. Eso te protege a ti y a tus servidores, sobre todo cuando aceptas regex de otras personas. ¿Ya identificaste algún patrón peligroso en tu código? Cuéntame en los comentarios cómo lo vas a corregir.