Коротко:
- XSS (Cross-Site Scripting) — це вразливість, коли зловмисник вставляє шкідливий скрипт у веб-сторінку, яку потім виконує браузер іншої людини.
- Існує три основні типи: reflected (відбитий), stored (збережений) і DOM-based.
- Найчастіше атака дозволяє вкрасти сесійні cookies, змінити вміст сторінки або виконувати дії від імені користувача.
- Основний захист — правильне кодування вихідних даних залежно від контексту + Content Security Policy.
- XSS досі входить до найпоширеніших проблем веб-додатків за даними OWASP.
- Навіть сучасні фреймворки не рятують, якщо розробник обходить їхні механізми екранування.
XSS-атака — це спосіб змусити браузер користувача виконати чужий JavaScript-код у контексті довіреного сайту. Зловмисник не зламує сервер напряму. Він підсовує скрипт через поле введення, URL або інший канал, а сайт повертає цей скрипт як частину своєї сторінки. Браузер довіряє домену і запускає код.
Саме тому XSS працює навіть тоді, коли сервер повністю захищений від SQL-ін’єкцій чи віддаленого виконання коду. Атака відбувається на стороні клієнта. У практиці я неодноразово бачив, як прості пошукові форми без екранування ставали точкою входу для таких атак. Один невірно оброблений параметр — і можна викрасти сесію адміністратора.
Розуміти механізм важливо і розробникам, і тим, хто тестує безпеку, і навіть звичайним користувачам, які помічають дивну поведінку сайтів. Далі розберемо, як саме це працює, які бувають різновиди і що реально допомагає захиститися.
Як працює XSS-атака
Суть атаки в тому, що веб-додаток приймає дані від користувача і вставляє їх у HTML-сторінку без належної перевірки та кодування. Браузер бачить скрипт, який нібито прийшов із довіреного джерела, і виконує його з правами цього сайту.
Типовий сценарій виглядає так. Зловмисник знаходить місце, де введені дані відображаються назад — пошук, коментар, ім’я користувача, повідомлення про помилку. Він підставляє фрагмент на кшталт … або більш витончений варіант через атрибути подій. Коли жертва відкриває сторінку, скрипт виконується. Він може прочитати cookies, відправити їх на сервер атакуючого, змінити DOM, підмінити форми або виконати запити від імені користувача.
Важливий момент: same-origin policy, яку браузери суворо дотримуються, тут обходиться саме тому, що код виконується «всередині» довіреного походження. Саме це робить XSS особливо небезпечним порівняно з багатьма іншими клієнтськими атаками.
Три основні типи XSS
Класифікація, яку використовують OWASP і PortSwigger, виділяє три головні форми. Вони відрізняються місцем зберігання payload і тим, хто ініціює виконання.
Reflected XSS (відбитий)
Це найпростіший і найпоширеніший варіант. Шкідливий код приходить у HTTP-запиті (зазвичай через параметр URL або тіло форми) і одразу повертається у відповіді сервера без збереження. Атакуючий надсилає жертві спеціально складене посилання. Людина клікає — і скрипт виконується.
Класичний приклад — пошукова форма, яка виводить «Ви шукали: [введений текст]». Якщо текст не екранується, можна вставити тег script. У практиці такі вразливості часто зустрічаються на сторінках помилок і в старих адмінках.
Небезпека reflected XSS у тому, що її легко поширювати через фішинг. Але payload не зберігається, тому атака разова для кожної жертви.
Stored XSS (збережений або постійний)
Тут скрипт зберігається на сервері — у базі даних, файлі, коментарі, профілі користувача, тікеті підтримки. Кожен, хто відкриває заражену сторінку, автоматично виконує payload. Це найнебезпечніший тип, бо один успішний запис може вразити сотні чи тисячі людей, включно з адміністраторами.
Типові місця: форуми, блоги з коментарями, чати, системи заявок. Якось доводилося розслідувати випадок, коли зловмисник залишив payload у полі «додаткова інформація» заявки. Коли оператор відкривав її в адмін-панелі — сесія адміністратора йшла на сторону.
DOM-based XSS
У цьому випадку сервер може взагалі не бачити шкідливого коду. Все відбувається на стороні клієнта. JavaScript додатку читає дані з URL (location.hash, location.search), postMessage, localStorage чи document.referrer і вставляє їх у DOM через небезпечні методи — innerHTML, document.write, eval тощо.
Серверні фільтри тут майже не допомагають. Атака часто залишається непоміченою під час звичайного тестування запит-відповідь. Саме тому DOM XSS вимагає аналізу клієнтського коду і відстеження потоку даних від джерела до sink.

Порівняння типів XSS
| Тип | Де живе payload | Хто запускає | Складність виявлення | Рівень ризику |
|---|---|---|---|---|
| Reflected | У поточному запиті | Жертва клікає на посилання | Середня | Середній |
| Stored | У базі / на сервері | Будь-хто, хто відкриває сторінку | Висока (потрібно перевіряти збереження) | Високий / критичний |
| DOM-based | У браузері (джерело → sink) | Завантаження сторінки з payload у URL | Висока (потрібен аналіз JS) | Середній–високий |
Таблиця показує, чому stored XSS часто вважають найгіршим варіантом. Він працює без додаткових дій жертви після того, як payload потрапив у систему.
Що може зробити зловмисник через XSS
Після успішного виконання скрипта можливості майже необмежені в межах того, що може робити легітимний код сайту:
- Викрадення сесійних cookies і токенів (якщо вони не захищені прапорцем HttpOnly).
- Підміна вмісту сторінки — фейкові форми входу, повідомлення про «оновлення пароля».
- Виконання дій від імені користувача — переказ коштів, зміна налаштувань, видалення даних.
- Ключовий логгер або перехоплення введених даних.
- Поширення черв’яка, якщо є можливість залишати контент від імені жертви.
У випадках, коли жертвою стає адміністратор, атака може перерости в повний контроль над додатком. Саме тому stored XSS у зонах з підвищеними привілеями особливо критичні.
Як захищатися від XSS
Захист будується на кількох шарах. Один механізм рідко буває достатнім.
Контекстне кодування вихідних даних
Це основний і найнадійніший спосіб. Дані, які потрапляють у HTML, треба екранувати залежно від місця вставки:
- У тілі HTML — екранувати < > & ” ‘
- В атрибутах — додатково враховувати лапки і спеціальні символи
- У JavaScript-контексті — використовувати правильне JS-екранування
- У URL — percent-encoding
Більшість сучасних шаблонізаторів і фреймворків (React, Angular, Vue, Twig, Blade) роблять це автоматично, якщо не обходити їхні механізми. Проблема починається саме тоді, коли розробник використовує dangerouslySetInnerHTML, v-html або аналогічні «обхідні шляхи» без додаткової санітизації.
Санітизація HTML
Коли потрібно дозволяти користувачам вводити розмітку (коментарі з форматуванням, редактори), використовують бібліотеки на кшталт DOMPurify. Вони залишають лише безпечні теги й атрибути. Важливо не мутувати результат після санітизації — інакше можливий mutation XSS.
Content Security Policy (CSP)
CSP — потужний додатковий бар’єр. Правильно налаштована політика може заборонити виконання інлайн-скриптів і завантаження скриптів з невідомих джерел. Найкращий варіант — nonces або hashes разом із strict-dynamic. CSP не замінює кодування, але суттєво зменшує шкоду навіть при наявності вразливості.
Trusted Types
Сучасний механізм браузерів, який забороняє небезпечним sink’ам (innerHTML, eval тощо) приймати звичайні рядки. Дані мають проходити через політику Trusted Types. Це один з найефективніших способів закрити DOM-based XSS на рівні платформи.

Типові помилки, які залишають двері відкритими
За досвідом тестування і розбору інцидентів найчастіше трапляються такі промахи:
- Екранування лише на вході, а не на виході. Фільтрація «поганих» слів легко обходиться.
- Використання blacklist замість whitelist. Нові вектори з’являються постійно.
- Довіра до даних, які «вже збережені в базі». Stored XSS народжується саме тут.
- Ігнорування DOM-джерел — hash, postMessage, document.referrer.
- Завантаження SVG без конвертації в растр. SVG може містити скрипти.
- Відключення HttpOnly на cookies «для зручності» JavaScript.
Окремо варто згадати про сліпий XSS (blind XSS). Payload зберігається і виконується пізніше в іншій частині системи — наприклад, у панелі адміністратора або в системі моніторингу. Виявити його складніше, бо зворотного зв’язку немає. Для таких випадків використовують спеціальні інструменти з out-of-band детекцією.
Що робити розробнику прямо зараз
Якщо ви підтримуєте існуючий проєкт, почніть з аудиту місць, де користувацькі дані потрапляють у відповідь. Перевірте всі шаблони, пошук, профілі, коментарі, повідомлення про помилки. Увімкніть CSP у режимі report-only і подивіться, що блокується. Для нових проєктів одразу використовуйте фреймворки з автоматичним екрануванням і не обходьте їх без вагомої причини.
Тестувальникам варто комбінувати ручний аналіз з автоматизованими сканерами, але не покладатися лише на них. DOM-based і сліпі XSS часто вислизають від чисто серверних перевірок. Корисні ресурси для глибшого вивчення — матеріали PortSwigger Web Security Academy і шпаргалки OWASP щодо запобігання XSS.
XSS не зникне найближчим часом. Поки веб-сторінки змішують дані, розмітку і код, ця вразливість залишатиметься актуальною. Але її можна зробити рідкісною і малоефективною, якщо дотримуватися простих, але послідовних правил кодування і політики безпеки. Перевірте свої форми й шаблони сьогодні — завтра може бути пізно.