XSS guide
Guía sobre XSS
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Explicación técnica de la vulnerabilidad XSS. Detallamos cómo identificar y explotar esta vulnerabilidad, tanto manualmente como con herramientas automatizadas. Además, exploramos estrategias clave para prevenirla
¿Qué es un XSS?
La vulnerabilidad de Cross-site scripting (XSS) es una vulnerabilidad de seguridad web que permite a un atacante comprometer las interacciones que los usuarios tienen con una aplicación vulnerable. Permite evadir la política de mismo origen (same origin policy), diseñada para separar diferentes sitios web entre sí
Los ataques XSS normalmente permiten que un atacante suplante la identidad de un usuario víctima, realice cualquier acción que este pueda ejecutar y acceda a sus datos. Si el usuario afectado tiene privilegios elevados dentro de la aplicación, el atacante podría hacerse con el control total de toda su funcionalidad y datos
¿Para qué puede usarse XSS?
Un atacante que explota una vulnerabilidad de cross-site scripting normalmente puede llevar a cabo estas acciones
Hacerse pasar por el usuario víctimaRealizar cualquier acciónque el usuario pueda ejecutarLeer cualquier datoal que el usuario tenga accesoCapturar las credenciales de inicio de sesióndel usuarioHacer un
defacementdel sitio webInyectar un funcionalidad tipo troyanoen el sitio web
PoC de XSS
Podemos confirmar la mayoría de las vulnerabilidades de XSS inyectando un payload que haga que nuestro propio navegador ejecute algún JavaScript arbitrario. Desde hace tiempo, es común usar la función alert() para este propósito porque es corta, inofensiva y difícil de pasar por alto cuando se ejecuta con éxito
Sin embargo, hay un inconveniente si usamos Chrome. A partir de la versión 92, los cross-origin iframes tienen prohibido llamar a alert() y como estos se utilizan para construir algunos de los ataques XSS más avanzados, a veces debemos usar un payload alternativo de PoC. Es por esto, que se recomienda la función print()
Contexto
A la hora de testear un reflected XSS o stored XSS, un paso clave es identificar el contexto, para ello, lo primero que necesitamos saber es dónde se refleja nuestro input. Existen estos tipos de contexto :
XSS entre etiquetas HTML
XSS dentro de los atributos de etiquetas HTML
XSS dentro de código JavaScript
Finalizar el script existente
Escapar de una string
Hacer uso de HTML-encoding
Inyectar expresiones de JavaScript en template literals
Tipos de ataques XSS
Existen tres tipos principales de ataques XSS:
Reflected XSS– Ocurre cuando losdatos proporcionados por el usuariose incluyen inmediatamente en larespuesta del servidorsin lavalidaciónoescapeadecuadosStored XSS– Sucede cuando losdatos maliciososenviados por el usuario sealmacenan en el servidor(por ejemplo, en unabase de datos) y luego se sirven a otros usuarios sin lasanitizaciónadecuadaDOM Based XSS– Este tipo devulnerabilidadse origina en ellado del cliente, donde elcódigo JavaScriptmanipula de forma insegura elDOM de la web, permitiendo la ejecución descripts maliciosos
Reflected XSS
Reflected XSS into HTML context with nothing encoded - https://justice-reaper.github.io/posts/XSS-Lab-1/ (XSS entre etiquetas HTML)
Reflected XSS into attribute with angle brackets HTML-encoded - https://justice-reaper.github.io/posts/XSS-Lab-7/ (XSS dentro de los atributos de etiquetas HTML)
Reflected XSS into a JavaScript string with angle brackets HTML encoded - https://justice-reaper.github.io/posts/XSS-Lab-9/ (XSS dentro de código JavaScript - Escapar de una string)
Reflected XSS into HTML context with most tags and attributes blocked - https://justice-reaper.github.io/posts/XSS-Lab-14/ (XSS entre etiquetas HTML)
Reflected XSS into HTML context with all tags blocked except custom ones - https://justice-reaper.github.io/posts/XSS-Lab-15/ (XSS entre etiquetas HTML)
Reflected XSS with some SVG markup allowed - https://justice-reaper.github.io/posts/XSS-Lab-16/ (XSS entre etiquetas HTML)
Reflected XSS in canonical link tag - https://justice-reaper.github.io/posts/XSS-Lab-17/ (XSS dentro de los atributos de etiquetas HTML)
Reflected XSS into a JavaScript string with single quote and backslash escaped - https://justice-reaper.github.io/posts/XSS-Lab-18/ (XSS dentro de código JavaScript - Finalizar el script existente)
Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped - https://justice-reaper.github.io/posts/XSS-Lab-19/ (XSS dentro de código JavaScript - Escapar de una string)
Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped - https://justice-reaper.github.io/posts/XSS-Lab-21/ (XSS dentro de código JavaScript - Inyectar expresiones de JavaScript en template literals)
Stored XSS
Stored XSS into HTML context with nothing encoded - https://justice-reaper.github.io/posts/XSS-Lab-2/ (XSS entre etiquetas HTML)
Stored XSS into anchor href attribute with double quotes HTML-encoded - https://justice-reaper.github.io/posts/XSS-Lab-8/ (XSS dentro de los atributos de etiquetas HTML)
Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped - https://justice-reaper.github.io/posts/XSS-Lab-20/ (XSS dentro de código JavaScript - Hacer uso de HTML-encoding)
Dom Based XSS
DOM XSS in document.write sink using source location.search - https://justice-reaper.github.io/posts/XSS-Lab-3/
DOM XSS in innerHTML sink using source location.search - https://justice-reaper.github.io/posts/XSS-Lab-4/
DOM XSS in jQuery anchor href attribute sink using location.search source - https://justice-reaper.github.io/posts/XSS-Lab-5/
DOM XSS in jQuery selector sink using a hashchange event - https://justice-reaper.github.io/posts/XSS-Lab-6/
DOM XSS in document.write sink using source location.search inside a select element - https://justice-reaper.github.io/posts/XSS-Lab-10/
DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded - https://justice-reaper.github.io/posts/XSS-Lab-11/
Reflected DOM XSS - https://justice-reaper.github.io/posts/XSS-Lab-12/
Stored DOM XSS - https://justice-reaper.github.io/posts/XSS-Lab-13/
Sinks que pueden conducir a vulnerabilidades de tipo DOM XSS
Los siguientes son algunos de los principales sinks que pueden provocar vulnerabilidades de tipo DOM XSS
1
2
3
4
5
6
7
document.write()
document.writeln()
document.domain
element.innerHTML
element.outerHTML
element.insertAdjacentHTML
element.onevent
Las siguientes funciones de jQuery algunas de los principales sinks que pueden provocar vulnerabilidades de tipo DOM XSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
add()
after()
append()
animate()
insertAfter()
insertBefore()
before()
html()
prepend()
replaceAll()
replaceWith()
wrap()
wrapInner()
wrapAll()
has()
constructor()
init()
index()
jQuery.parseHTML()
$.parseHTML()
Explotar vulnerabilidades XSS
La forma tradicional de demostrar que hemos encontrado una vulnerabilidad de cross-site scripting es crear un popup usando la función alert(). Esto no es porque el XSS tenga algo que ver con los popups, es simplemente una forma de demostrar que podemos ejecutar código JavaScript arbitrario en un dominio dado. Puede que notes que algunas personas usan alert(document.domain). Esto sirve para dejar explícito en qué dominio se está ejecutando el JavaScript
Para demostrar que una vulnerabilidad XSS es una amenaza real proporcionando un exploit completo. En esta sección exploraremos tres de las formas más populares de explotar un XSS
Robar cookies
Robar cookies es una forma tradicional de explotar XSS. La mayoría de las aplicaciones web usan cookies para el manejo de sesiones. Podemos explotar vulnerabilidades de cross-site scripting para enviar las cookies de la víctima a nuestro propio dominio, luego inyectar manualmente las cookies en el navegador y suplantar a la víctima
En la práctica, este método tiene algunas limitaciones importantes:
La víctima podría no estar
logueadaMuchas aplicaciones ocultan sus
cookiesde JavaScript usando laflag HttpOnlyLas
sesionespueden estarvinculadasa factores adicionales, como ladirección IP del usuarioLa
sesiónpuedeexpirarantes de que podamossecuestrarla
En este laboratorio podemos ver como aplicar esta técnica:
- Exploiting cross-site scripting to steal cookies - https://justice-reaper.github.io/posts/XSS-Lab-22/
Capturar contraseñas
Hoy en día, muchos usuarios tienen gestores de contraseñas que autocompletan sus contraseñas. Podemos aprovechar esto creando un campo de contraseña, leyendo la contraseña autocompletada y enviándola a nuestro propio dominio. Esta técnica evita la mayoría de los problemas asociados con robar cookies y puede incluso obtener acceso a todas las demás cuentas donde la víctima haya reutilizado la misma contraseña
La principal desventaja de esta técnica es que solo funciona con usuarios que tienen un gestor de contraseñas que realiza autocompletado. Si un usuario no tiene guardada una contraseña, podemos intentar obtenerla mediante un ataque de phishing
En este laboratorio podemos ver como aplicar esta técnica:
- Exploiting cross-site scripting to capture passwords - https://justice-reaper.github.io/posts/XSS-Lab-23/
Bypassear las protecciones contra CSRF
Un XSS permite a un atacante hacer casi todo lo que un usuario legítimo puede hacer en un sitio web. Al ejecutar código JavaScript arbitrario en el navegador de la víctima, el XSS nos permite realizar una amplia variedad de acciones como si fuéramos ese usuario. Por ejemplo, podemos hacer que la víctima envíe un mensaje, acepte una solicitud de amistad o transfiera algunos Bitcoins
Algunos sitios web permiten a los usuarios logueados cambiar su dirección de correo electrónico sin volver a ingresar su contraseña. Si encontramos un XSS en uno de estos sitios web, podemos explotarlo para robar un token CSRF. Con ese token, podemos cambiar el correo electrónico de la víctima a una que controlemos. Luego, podemos activar un restablecimiento de contraseña para obtener acceso a la cuenta
Este tipo de exploit combina XSS (para robar el token CSRF) con la funcionalidad normalmente atacada por CSRF. Mientras que el CSRF tradicional es una vulnerabilidad de “una sola vía”, donde el atacante puede inducir a la víctima a enviar peticiones pero no puede ver las respuestas, el XSS permite una comunicación bidireccional. Esto permite al atacante tanto enviar peticiones como leer las respuestas, resultando en un ataque híbrido que evade las defensas anti-CSRF
En este laboratorio podemos ver como aplicar esta técnica:
- Exploiting XSS to bypass CSRF defenses - https://justice-reaper.github.io/posts/XSS-Lab-24/
Cheatsheet
Usaremos estas cheatsheet para facilitar la detección y explotación de esta vulnerabilidad:
- Hacking tools https://justice-reaper.github.io/posts/Hacking-Tools/
¿Cómo detectar y explotar un XSS?
Teniendo en cuenta que los términos y herramientas mencionados a continuación se encuentran en la cheatsheet mencionada anteriormente, llevaremos a cabo los siguientes pasos:
Instalarlas extensionesActive Scan ++,Error Message Checks,Additional Scanner Checks,Collaborator EverywhereyBackslash Powered ScannerdeBurpsuiteAñadireldominioy sussubdominiosalscopeHacer un
escaneo generalconBurpsuite. Comotipo de escaneomarcaremosCrawl and audity comoconfiguración de escaneousaremosDeepEscanearemos partes específicas de la peticiónusando elescáner de Burpsuite. Paraescanearlosinsertion pointsdebemos seleccionar entipo de escaneola opciónAudit selected itemsCon el objetivo de encontrar
vulnerabilidadesde tipoDOM XSSusamos elDOM Invaderpara testear todos losinputsUna vez hecho esto, usamos
XSStrikecon el parámetro--crawlpara poderidentificarsi hay algunavulnerabilidado algúnsink, el cual pueda conducir a unXSSUsamos
XSStrikenuevamente, pero esta vez con el objetivo dedetectarunXSSSi no encontramos nada y la
URLes de este estilohttps://0a42008c0326fbeb803d129600e6006e.web-security-academy.net/?search=testo de este otrohttp://stock.0a1b001e03ee4b4480f30dd1005a0015.web-security-academy.net/?productId=3&storeId=1, vamos a usarLoxsyXSSuccessorSi no encontramos nada, usaremos
LoxsyXSSuccessornuevamente pero con losdiccionariosque se muestranhacking toolsque contenganpayloadsparaXSS. A estosdiccionariostambién le podemos añadir el dePortswiggerque podemosobtenermedianteDalfoxy los dePayloadsAllTheThings. PodemoslistarlospayloadsdePortswiggercon este comandodalfox --remote-payloadboxSi
LoxsyXSSuccessorno encuentran nada, usaremosDalfox, el cual tiene soporte paraDOM XSS,Reflected XSSyStored XSS. Como dije anteriormente,Dalfoxcuenta con lospayloadsdePortswiggerpara descubrirXSSy con losdiccionariosdeBurpsuiteyAssetnotepara descubrirparámetrosen laURLque seanvulnerablesSi sospechamos de un
stored XSS, podemos usar lospayloadsdeLoxs,XSSuccessor, los dePortswiggerque se obtienen medianteDalfox, los de losdiccionariosdehacking toolso los dePayloadsAllTheThingscon elIntruderdeBurpsuite. Si necesitamosinyectar payloadsenvarias posicionesseleccionaremosPitchforkcomotipo de ataque.Puede ser complicado encontrar el payload correcto si mandamos muchos a la vez, por eso, recomiendo usar la herramientapayloadSplitterparadividirunagran lista de payloadsenlistas mucho más pequeñas y manejablesEn el caso en que haya algunos
tagsoatributosblacklisteados, podemos usarXSSDynaGeno elfuzzer de XSStrikepara ver quecaracterespodemos usar. Sin embargo, yo prefiero usar elIntruderdeBurpsuitejunto con lacheatsheet de Portswiggerpara averiguarlo, debido a que esta forma es másprecisaSi no encontramos nada, nos centraremos en buscar los
XSS de forma manualutilizando la metodología deHacktricksy usando como apoyo lascheatsheetsdePortswiggery dePayloadsAllTheThings
Prevenir ataques XSS
La prevención de XSS puede lograrse, en general, mediante dos capas de defensa:
Codificarlosdatosen lasalidaValidarelinput de datosalrecibirlo
La codificación debe aplicarse justo antes de que los datos controlados por el usuario se escriban en una página, porque el contexto en el que se escriben determina el tipo de codificación que debemos usar. Por ejemplo, los valores dentro de una cadena JavaScript requieren un tipo de escapado diferente al de un contexto HTML
En un contexto HTML, debemos convertir los valores no permitidos en entidades HTML
<se convierte en:<>se convierte en:>
En un contexto de cadena JavaScript, los valores no alfanuméricos deben escaparse en Unicode
<se convierte en:\u003c>se convierte en:\u003e
A veces, debemos aplicar múltiples capas de codificación, en el orden correcto. Por ejemplo, para incrustar de forma segura el input del usuario dentro de un manejador de eventos, debemos tratar tanto el contexto JavaScript como el contexto HTML. En este caso, primero debemos escapar en Unicode el input y luego codificarlo en HTML
1
<a href="#" onclick="x='This string needs two layers of escaping'">test</a>
Validar el input al recibirlo
La codificación probablemente sea la línea de defensa más importante contra XSS, pero no es suficiente para prevenir vulnerabilidades en todos los contextos. También debemos validar el input de la forma más estricta posible en el momento en que se recibe por primera vez del usuario
Ejemplos de validación de input:
Si un
usuarioenvía unaURLque se devolverá en lasrespuestas, validar queempiece con un protocolo segurocomoHTTPoHTTPS. De lo contrario, alguien podríaexplotar el sitiousando un protocolo peligroso comojavascriptodataSi un
usuarioproporciona unvalorque se espera seanumérico, validar que el valor contenga realmente unenteroValidar que el
inputsolo contenga unconjunto de caracteres esperado
La validación del input debería funcionar bloqueando el input no válido. La alternativa que consiste en intentar limpiar el input inválido para hacerlo válido, es más propensa a errores y debe evitarse siempre que sea posible
Whitelisting vs Blacklisting
La validación de input debe usar preferiblemente whitelists en lugar de blacklists. Por ejemplo, en lugar de intentar crear una lista con todos los protocolos peligrosos (javascript, data, etc.), debemos hacer una lista con los protocolos seguros (HTTP, HTTPS) y bloquear todo lo que no esté en la lista. Esto asegura que la defensa no falle cuando aparezcan nuevos protocolos peligrosos y reduce el riesgo frente a ataques que intenten ofuscar valores inválidos para evadir la blacklist
Permitir HTML “seguro”
Permitir que los usuarios publiquen HTML debería evitarse siempre que sea posible, aunque a veces es un requisito de negocio. Por ejemplo, un blog podría permitir que los comentarios contengan HTML limitado
El enfoque clásico es intentar filtrar las etiquetas peligrosas y JavaScript. Podemos implementar esto usando una whitelist de etiquetas y atributos seguros, pero debido a las diferencias en los motores de parseo de navegadores y a técnicas como mutation XSS, este método es extremadamente difícil de implementar de forma segura
La opción menos mala es usar una librería JavaScript que realice el filtrado y la codificación en el navegador del usuario, como DOMPurify. Otras librerías permiten que los usuarios escriban en Markdown y luego lo convierten a HTML. Sin embargo, todas estas librerías tienen vulnerabilidades XSS de vez en cuando, por lo que no es una solución perfecta. Por lo tanto, si usamos una, debemos vigilar de cerca las actualizaciones de seguridad
Además de JavaScript, CSS e incluso HTML pueden ser peligrosos en algunas situaciones https://portswigger.net/research/detecting-and-exploiting-path-relative-stylesheet-import-prssi-vulnerabilities#badcss
Prevenir XSS usando un motor de plantillas
Muchos sitios web modernos usan motores de plantillas del lado del servidor como Twig o Freemarker para incrustar contenido dinámico en HTML. Estos suelen tener su propio sistema de escapado, por ejemplo, en Twig podemos usar el filtro e() con un argumento que define el contexto
1
2
3
{{ user.firstname | e('html') }}
Otros motores de plantillas, como Jinja o React, escapan el contenido dinámico por defecto, lo que previene la mayoría de los casos de XSS. Se recomienda revisar cuidadosamente las funciones de escapado al evaluar si usar un motor de plantillas o framework concreto
Si se concatena directamente el input del usuario en templates strings, nos volvemos vulnerables a SSTI (Server-Side Template Injection), que a menudo es más grave que el XSS
Prevenir XSS en PHP
En PHP existe la función incorporada htmlentities para codificar entidades. Debemos llamarla para escapar el input cuando estemos en un contexto HTML. Debe llamarse con tres argumentos:
El
inputENT_QUOTES, una bandera que indica que se deben codificar todas lascomillasEl
conjunto de caracteres, que normalmente debe serUTF-8
1
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>
En un contexto de cadena JavaScript, debemos escapar en Unicode el input. Desafortunadamente, PHP no incluye una API para hacer Unicode-escape a un string. Así se puede implementar manualmente en PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
function jsEscape($str) {
$output = '';
$str = str_split($str);
for ($i = 0; $i < count($str); $i++) {
$chrNum = ord($str[$i]);
$chr = $str[$i];
// Manejo de caracteres especiales Unicode (U+2028, U+2029)
if ($chrNum === 226) {
if (isset($str[$i+1]) && ord($str[$i+1]) === 128) {
if (isset($str[$i+2]) && ord($str[$i+2]) === 168) {
$output .= '\u2028';
$i += 2;
continue;
}
if (isset($str[$i+2]) && ord($str[$i+2]) === 169) {
$output .= '\u2029';
$i += 2;
continue;
}
}
}
// Escapado de caracteres especiales
switch ($chr) {
case "'":
case '"':
case "\n":
case "\r":
case "&":
case "\\":
case "<":
case ">":
$output .= sprintf("\\u%04x", $chrNum);
break;
default:
$output .= $str[$i];
break;
}
}
return $output;
}
?>
Así se usa la función jsEscape en PHP
1
<script>x = '<?php echo jsEscape($_GET['x'])?>';</script>
Alternativamente, podríamos usar un motor de plantillas
Prevenir XSS del lado del cliente en JavaScript
Para escapar el input del usuario en un contexto HTML en JavaScript, necesitamos un codificador HTML propio porque JavaScript no proporciona una API para codificar HTML. Esta función de JavaScript convierte una cadena en entidades HTML
1
2
3
4
5
6
function htmlEncode(str) {
return String(str)
.replace(/[^\w. ]/gi, function(char) {
return '&#' + char.charCodeAt(0) + ';';
});
}
Podríamos usar la función anterior así:
1
<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>
Si el input está dentro de una cadena JavaScript, necesitaremos un codificador que realice el escape de los caracteres en Unicode. Este sería un ejemplo:
1
2
3
4
5
function jsEscape(str) {
return String(str).replace(/[^\w. ]/gi, function(c) {
return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
});
}
Podríamos usar la función anterior así:
1
<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>
Prevenir XSS en jQuery
La forma más común de XSS en jQuery ocurre cuando pasamos el input del usuario a un selector de jQuery. Los desarrolladores web a menudo usaban location.hash y lo pasaban al selector, lo que causaba un XSS porque jQuery interpretaba ese contenido como HTML
jQuery reconoció este problema y corrigió la lógica del selector para verificar si el input comienza con un símbolo de hash (#). Ahora, jQuery solo renderiza HTML si el primer carácter es un <
Si le pasamos datos no confiables al selector de jQuery, debemos asegurarnos de escapar correctamente el valor usando la función jsEscape
Mitigación de XSS usando la política de seguridad de contenido (CSP)
La política de seguridad de contenido (CSP) es la última línea de defensa contra el cross-site scripting. Si la prevención de XSS falla, podemos usar CSP para mitigar XSS restringiendo lo que un atacante puede hacer
CSP permite controlar varias cosas, por ejemplo, si se pueden cargar scripts externos y si se ejecutarán inline scripts. Para desplegar CSP, necesitaremos incluir un encabezado HTTP llamado Content-Security-Policy con un valor que contenga tu política
Un ejemplo de CSP sería este:
1
default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';
Esta política especifica que los recursos, como imágenes y scripts, solo pueden cargarse desde el mismo origen que la página principal. Por lo tanto, aunque un atacante logre inyectar un payload XSS, solo podrá cargar recursos desde el origen actual. Esto reduce significativamente la posibilidad de que un atacante pueda explotar el XSS
Si necesitamos cargar recursos externos, debemos asegurarnos de permitir solo scripts que no ayuden al atacante a explotar nuestro sitio web. Por ejemplo, si permitimos explícitamente ciertos dominios, un atacante podría cargar cualquier script desde esos dominios. Siempre que sea posible, debemos alojar los recursos en nuestro propio dominio
Si eso no es posible, podemos usar una política basada en hash o nonce para permitir scripts en diferentes dominios. Un nonce es una cadena aleatoria que se añade como atributo a un script o recurso, y solo se ejecutará si esa cadena coincide con la generada por el servidor. Un atacante no puede adivinar esa cadena aleatoria, por lo que no podrá ejecutar un script o recurso con un nonce válido
