N-D Por David Kotriksnov (a.k.a. SH4V) N3t-Datagrams.net
En los últimos años se ha hablado mucho de ataques de Denegación de Servicio y más de alguno de vosotros conoceréis las famosas botnets (redes de maquinas zombies programadas para realizar peticiones masivas a servidores hasta causar una sobresaturación de los mismos). Existen sin embargo otro tipo diferente de ataques de Denegación de Servicio o DoS (Denial of Service). Un ataque contra una base de datos que eliminara el contenido de sus tablas sería otro tipo de denegación de servicio ya que no permitiría al usuario poder acceder a la información antes almacenada en el servidor: DROP database Existen muchos tipos de Denegaciones de Servicio pero la variante que vamos a tratar en este paper son las Denegaciones de Servicio en la validación de Expresiones Regulares, o lo que se conoce por redos. En 2009 fue presentado en la Open Web Application Security Project (OWASP) Israel Conference. En esa presentación se explicaba como una expresión regular pobremente escrita podía ser explotada para realizar un DoS. TIPOS DE MOTORES REGEXP: 2 Podemos encontrar dos tipos de motores RegExp: DFA (Deterministic Finite Automaton) y NFA (Nondeterministic Finite Automaton). Los motores NFA son sistemas de marcha atrás mientras que los DFA no. Los DFA evalúan cada carácter de cada cadena como mucho una vez mientras que los NFA pueden evaluar cada carácter todas las veces que sean necesarias hasta determinar que la cadena ha finalizado o la coincidencia ha sido encontrada. El sistema de marcha atrás del motor NFA tiene el pro de que pueden procesar expresiones regulares más complejas pero tienen el inconveniente de superar en tiempo de procesamiento a las DFA (depende de la expresión regular). EL PROBLEMA: Los sistemas NFA (marcha atrás) pueden confirmar con rapidez una coincidencia, sin embargo identificar una no-coincidencia en la cadena puede tomar mucho más tiempo ya que el motor tiene que confirmar que ninguna de los posibles caracteres a través de la string coincide con la expresión regular. Es decir, que todos los caracteres tienen que volver a ser testeados. Por tanto,
cada vez que añadamos un carácter a la cadena, el tiempo de respuesta se duplicará de forma exponencial Vamos a ver un ejemplo sencillo. Utilizaremos las expresiones regulares en Javascript porque no necesitamos más que un navegador para poder procesarlas. Esto hace que sea más portable y cualquiera en su casa sin necesidad de un servidor pueda probarlo: <script language='javascript'> var str="abcdefg"; var reg=/^(\w+)+$/; if (reg.test(str)){ alert('match found!'); else{ alert('match not found...') 3 </script> El código se ejecuta sin problemas y nos mostrará un alert con el mensaje "Match found!". Pero qué ocurriría si añadimos al final de la variable "str" un carácter no alfanumérico? Probad a sustituir la línea dos por esto: <script language='javascript'> var str="abcdefg^"; var reg=/^(\w+)+$/; if (reg.test(str)){ alert('match found!'); else{ alert('match not found...') </script>
El navegador se trabará. Algunos navegadores están preparados para este tipo de situaciones así que no os extrañéis si os sale un mensaje de alerta preguntando si quieres detener el script. Pero la pregunta es: Por qué? es entonces cuando llega el turno de bucear por el conocimiento para descubrir la respuesta al enigma que de forma superficial parece cosa de brujería, pero que en el fondo no es más que pura matemática. RESOLUCIÓN DEL PROBLEMA: Por partes, analicemos la expresión regular: \w: Cualquier carácter alfanumérico. +: 1 o más repeticiones de caracteres alfanuméricos. (): Toma los caracteres dentro del paréntesis como un grupo. El problema surge cuando incluimos caracteres que pueden repetirse dentro de un grupo que a su vez puede repetirse. Esto crea un bucle que crece de forma exponencial a medida que añadimos caracteres. Como se dijo anteriormente, los sistemas NFA son motores de marcha atrás. Esto es, que empiezan determinando la longitud de la cadena y comienzan a analizarla desde el final hasta el inicio. Tomemos el siguiente ejemplo: 4 <script> var str="abcdefg"; var reg=/^w+$/; reg.test(str); </script> El script comenzaría por calcular la longitud, empezando por a, b, c... hasta llegar a g. Una vez llegado a g, el sistema de marcha atrás del motor NFA vuelve hacia atrás, en el siguiente orden: abcdefg, abcdef, abcde, abcd, abc, ab, a. El número de rutas es de 7. Esta expresión regular no sería vulnerable a redos. Sin embargo la cosa cambia cuando la expresión regular es esta: <script> var str="abcdefg"; var reg=/^(w+)+$/; reg.test(str);
</script> Como veis, estamos metiendo entre paréntesis un grupo de caracteres alfanuméricos que puede repetirse como unidad y como grupo. Ahí es donde está el peligro. Veamos como interpretaría esto el motor NFA: A. Llega hasta el final de la cadena de la siguiente manera: a, ab, abc, abcd, abcde, abcdef, abcdefg. B. Comienza la marcha atrás. Analizaremos a partir de aquí diferenciando entre adyacentes y grupos. Como hacerlo en un procesador de textos es muy complicado, haré en papel los 4 primeros pasos. A partir de ahí pensad y si es necesario sacad bolígrafo y papel para entenderlo. Si compagináis la tabla de abajo con la imagen tendréis más facilidad a la hora de comprender. Adyacentes Grupos Resultado de Rutas/Ciclos abcdef + (g) - g es tomado como grupo. 1 ruta/ciclo. abcde + (fg) - fg es tomado como grupo (+1) que su vez se divide en un adyacente (f) y un grupo (g) (+1). 1+1=2 rutas/ciclos. abcd + (efg) - efg es tomado como grupo (+1), lo que significa que será analizado como grupo efg. efg a su vez será dividido en un adyacente (ef) y un grupo (g) (+1). Siguiendo la línea de la marcha atrás, se dará un paso a la izquierda quedando el adyacente (e) y el grupo (fg)(+1). Finalmente el grupo (fg) se analiza quedando un adyacente (f) y un grupo (g) (+1). 1+1+1+1=4 rutas/ciclos. abc + (defg) [...] 8 rutas/ciclos. ab + (cdefg) [...] 16 rutas/ciclos. a + (bcdefg) [...] 32 rutas/ciclos. - + (abcdefg) [...] 64 rutas/ciclos. TOTAL: 127 RUTAS + 1 ruta de identificación inicial= 128 RUTAS/CICLOS. 5
6 Cada vez que añadamos un carácter más, el número de rutas o repeticiones será el doble, es decir, crecerá en sentido exponencial al cuadrado, por lo que el número de rutas será igual a: N= 2 n Donde N es el número de rutas y n es el número de caracteres antes del fallo.
EXPLOTACIÓN: Vamos a ver varios ejemplos de RegExp vulnerables a una Denegación de Servicio (algunas de ellas sacadas de OWASP) y como explotarlas: /^(\w+)+$/= abcdefgasdfasdlkja_slf123leic_vaskjefhasjefh^ //Ingresamos caracteres alfanuméricos y/o "_" y finalizamos la cadena con una excepción. 2⁴⁴ rutas. /^(a+)*$/= aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^ //Ingresamos "a" y finalizamos con una excepción. 2³⁹ rutas. /^([a-z0-9]+)*$/= aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^ // 2³⁹ rutas. /^(1 r)+=$/= 1111111111111111111111111111111111111111^ //2⁴⁰ rutas. /^(([a-z])+.)+[a-z]([a-z])+$/= aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^ //2³⁹ rutas. 7 /^([a-za-z0-9])(([\-.] [_]+)?([a-za-z0-9]+))*(@){1[a-z0-9]+[.]{1(([az]{2,3) ([a-z]{2,3[.]{1[a-z]{2,3))$/= 12345678901234567890123456789012345678901234567890^ // 2⁵⁰ rutas. /^(.*a){x for x > 10^/ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^ //Esta es interesante. 2³⁹ rutas. Como podéis observar, se explotan todas del mismo modo: introduciendo caracteres válidos y añadiendo finalmente uno que no entre en el grupo de caracteres que deberían venir después. LOCALIZACIÓN: Ponernos a analizar una expresión regular en busca de posibles DoS puede ser algo tedioso si no estás familiarizado con las expresiones regulares. No cuesta nada ponerse y es hasta divertido. Para ahorrar la tarea de analizar manualmente he desarrollado un programa en JavaScript que analiza mediante RegExp si hay alguna falla de redos. Analizar una expresión regular por medio de RegExp de forma perfecta es muy complicado por lo que esta aplicación no es perfecta y puede dar falsos resultados. Aún así tiene un porcentaje de acierto muy alto, en torno al 95%. Sólo tienes que guardarlo en un archivo de texto y renombrarlo con extensión htm/html.
<html> <!-- [-] RegExp DoS Analyzer. [-] Programmed by SH4V. [-] Visit http://n3t-datagrams.net/ && http://foro.undersecurity.net/ [-] Gr33tz to Pr0x, Protos, Lix, OzX, C1c4Tr1Z, N0b0dy, Yoyahack, S[e]C, Seth, 1995, Dynamique and Undersec members. [-] Realizar un programa que localice RegExp vulnerables a un DoS mediante RegExp es extremadamente difícil por lo que el programa no es perfecto. En ocasiones da falsos positivos/negativos.4 [-] Enjoy it :-) --> 8 <head> <meta http-equiv='content-type' Content='text/html;charset=utf-8'> <style type="text/css"> #title { text-align:center; padding-top:15px; padding-bottom:15px; font-family: Arial, Georgia; font-size: 35px; color: white; background-color: #01356e; #form { background-color: #819ab8;
text-align:center; padding-bottom:1px; padding-top:10px; color: white; #result { background-color:#ffffff; color: #819ab8; font-family: Arial, Georgia; text-align:center; padding-bottom: 400px; #credits{ 9 text-align:center; font-family: Arial, Georgia; color: #01356e; font-size: 11px; font-weight: bold; </style> </head> <title>regexp Denial of Service Analyzer</title> <body> <div id='title'>regexp Denial of Service Analyzer</div> <div id='form'><form name='formu' method='post'> <input type='text' name='entrada' size= 37> <input type='button' onclick='proform()' value='comprobar RegExp'> </form></div> <div id='result'><h1><br /></h1></div>
<script> var reg= /\(([\w!%&,~:;<>=@ \/\'\- \"\\\*\+\?\ \{\[\]\\(\)\^\$\.\#])*\)\*/; var result=document.getelementbyid('result'); var per= 0; var tru= false; function cutpast(strg,sym){ strg=strg.split(sym); strg=strg.join('*'); return strg; 10 function proform(){ var str = document.formu.elements["entrada"].value; if (str.match(/script/i)){ alert('intentó un DOM bassed XSS. Si esta medida de seguridad interfiere en el análisis de su RegExp, desactívela modificando el código fuente.'); else{ countdown(); function countdown(){ if(tru){ var str = document.formu.elements["entrada"].value; str=cutpast(str,"+");
if (reg.test(str)){ control=reg.exec(str)[0]; control=cutpast(control, " "); if(control.split('*').length){ alert("posible redos! Revise las RegExp."); var sh0w= document.formu.elements["entrada"].value.split("\"").jo in('').split("\'").join(''); result.innerhtml="<h2>posible redos en: "+sh0w+"</h2>"; else{ alert('sin riesgo de alerta... :)'); result.innerhtml="<h2>no se encontraron patrones peligrosos pero se recomienda hacer una revisión manual.</h2>"; 11 else{ per++; if (per==100){tru=true; countd=settimeout('countdown()',25); result.innerhtml="<h1>"+per+"% Completado</h1>"; </script> <div id='credits'>n3t-datagrams / David Kotriksnov (a.k.a. SH4V)</div> </body> </html>
REDOS POR REGEXP INJECTION: Un tipo muy interesante de redos es este. Consiste en provocar una cadena vulnerable a redos y explotarla más adelante en un mismo formulario de verificación de campos. Veamos un ejemplo sacado de OWASP: String username = textbox1.text; String password = textbox2.text; Regex testpassword = new Regex(userName); Match match = testpassword.match(password); if (match.success) { MessageBox.Show("Do not include name in password."); 12 else { MessageBox.Show("Good password."); LECTURAS RECOMENDADAS: http://www.owasp.org/index.php/regular_expression_denial_of_se rvice_-_redos http://es.wikipedia.org/wiki/expresi%c3%b3n_regular AGRADECIMIENTOS: Gr33tz to Pr0x, Protos, Lix, OzX, C1c4Tr1Z, N0b0dy, Yoyahack, S[e]C, Seth, 1995, Dynamique and Undersec members.