Prueba expresiones regulares con resaltado de coincidencias.
Los resultados tienen carácter informativo. Contrástalos con otras fuentes.
Una cadena de 26 caracteres puede tardar 368 ms en fallar contra el patrón equivocado
Las expresiones regulares suelen ejecutarse en un tiempo proporcional a la entrada. Anida un cuantificador dentro de un grupo cuantificado y eso deja de ser cierto: /^(a+)+$/ tarda unos 23 ms en rechazar 22 caracteres, 95 ms con 24 y 368 ms con 26. Cada carácter adicional duplica aproximadamente el trabajo, así que una entrada algo más larga no ralentiza la búsqueda: impide que termine.
Cómo funciona
- Prueba un patrón sobre un texto de ejemplo y muestra cada coincidencia con sus grupos de captura.
- Explica qué hace cada parte de la expresión, ya que la sintaxis es densa por diseño.
- Permite probar los casos que fallan, que es donde viven los problemas de rendimiento.
la forma peligrosa: un cuantificador dentro de un grupo cuantificado (a+)+ (a*)* (a|a)* (\d+)+ n caracteres pueden repartirse entre los grupos de un número exponencial de formas, y el motor debe probarlas todas antes de poder informar de que no hay coincidencia equivalente seguro: a+ — un solo cuantificador, tiempo lineal
Ejemplo resuelto
Medición de /^(a+)+$/ sobre cadenas de aes seguidas de un ! que no puede coincidir.
- 22 caracteres → unos 23 ms
- 24 caracteres → unos 95 ms
- 26 caracteres → unos 368 ms
- las mismas 26 aes sin el ! → coincide en menos de 0,01 ms
- la forma segura /^a+$/ sobre 100.000 caracteres → 0,12 ms
Rechazar 26 caracteres cuesta tres mil veces más que aceptar 100.000. El éxito es instantáneo y el fallo exponencial, y por eso esto nunca aparece en las pruebas: el camino feliz siempre es rápido.
Cómo leer el resultado
- Es un vector de denegación de servicio dondequiera que la entrada del usuario llegue a un patrón. Un atacante no necesita una cadena larga; treinta o cuarenta caracteres bastan para ocupar un hilo de petición durante minutos, y un puñado de esas peticiones tumba un servidor.
- El arreglo casi siempre consiste en quitar el anidamiento en lugar de optimizar a su alrededor. (a+)+ y a+ reconocen exactamente el mismo lenguaje, así que la versión vulnerable no aporta absolutamente nada: es un error, no un compromiso.
- Las mediciones proceden de una máquina y varían según el motor, pero la forma del fenómeno no. Cualquier implementación con retroceso — JavaScript, Python, Java, PCRE — se comporta así. RE2 y el regexp de Go no, porque rechazan las funciones que lo hacen posible.
- Prueba tus patrones con entradas que fallen, no solo con las que coinciden. El caso catastrófico solo aparece cuando el motor debe agotar todas las posibilidades antes de concluir que no hay coincidencia.
Preguntas frecuentes
- ¿Cómo sé si mi patrón es vulnerable?
- Busca un cuantificador dentro de un grupo cuantificado — (a+)+, (a*)*, (\d+)* y similares — y alternativas cuyas ramas puedan reconocer el mismo texto. Después prueba con una cadena larga que CASI coincida, porque son los casi aciertos los que disparan el camino exponencial.
- ¿Por qué es rápido cuando coincide y lento cuando no?
- Porque el motor puede detenerse en el primer éxito, pero debe agotar todas las posibilidades antes de declarar un fallo. Con cuantificadores anidados el número de posibilidades se duplica por carácter, así que el caso que falla es el único que llega a explorarse por completo.