Когда мы говорим про безопасность веб-приложений, то межсайтовый скриптинг (XSS) оказывается одной из самых распространённых уязвимостей. XSS позволяет злоумышленникам внедрить вредоносный код прямо на сайт, с которым взаимодействуют пользователи. Это может быть как безобидное всплывающее окно, так и кража личных данных. В этой статье разберём, как работает XSS, почему браузеры доверяют вредоносному коду и что можно сделать, чтобы защитить сайт от таких атак.
Вся информация в статье дана исключительно в образовательных целях, все совпадения случайны, персонажи вымышлены, а ситуации никогда не встречаются в жизни. Пакостить — плохо.
Общее понимание XSS
Межсайтовый скриптинг (Cross-Site Scripting, XSS) — это уязвимость веб-приложений, которая позволяет злоумышленнику внедрить вредоносный код (malicious code) прямо на страницу, где с ним взаимодействуют другие пользователи. Проще говоря, хакер использует слабые и незащищённые места сайта, чтобы его скрипты выполнялись так, будто это часть самой страницы.
Главная опасность XSS — его незаметность. Пользователь заходит на привычный сайт и даже не подозревает, что там уже выполняется какой-то вредоносный код. Это может привести к проблемам.
- Злоумышленники могут перехватить важные данные — куки или токены доступа, которые используются для входа на сайт.
- Злоумышленник может действовать от имени жертвы, выполняя любые действия, доступные пользователю. Например, отправлять сообщения, менять настройки или совершать покупки.
- Через страницу могут незаметно загрузиться вирусы или другие вредоносные программы.
- Если у атакуемого пользователя есть особые права (например, администратора), злоумышленник может получить полный доступ ко всем функциям и данным веб-приложения.
По сути, XSS нарушает так называемую политику одного источника (same origin policy), которая изолирует сайты друг от друга и ограничивает выполнение JavaScript. Она позволяет скриптам взаимодействовать только с данными того сайта (origin), откуда они были загружены, включая HTML, CSS, куки и локальное хранилище. Это значит, что по идее JavaScript с одного сайта не может получить доступ к данным другого, предотвращая кражу информации и атаки. Например, сайт интернет-банка не может видеть, что вы делаете на другой вкладке с соцсетями. Но XSS может обойти эти ограничения и дать злоумышленникам доступ к данным, которые браузер считает доверенными.
В случае XSS злоумышленник с помощью уязвимости внедряет сторонний код, который «притворяется» частью атакуемого сайта. И если на сайте не предусмотрена защита от атак, то такой код может получить доступ к данным пользователя так, как будто это делает сам сайт, и использовать их в своих целях.
Так происходит, потому что браузер «доверяет» сайту, с которого загружена страница. Вредоносный код, внедрённый злоумышленником, становится частью HTML-документа и интерпретируется браузером как настоящая часть сайта. В результате браузер выполняет этот код автоматически, не различая, был он добавлен разработчиком или злоумышленником.
Схема работы XSS
Чтобы понять, как работает XSS, разберём его основные типы. Они отличаются способом внедрения вредоносного кода, его местом хранения и воздействием на пользователей.
Типы XSS-уязвимостей
Есть три основных типа XSS: отражённые (непостоянные), хранимые (постоянные) и DOM-модели.
Отражённые (непостоянные) XSS
Отражённая XSS-уязвимость возникает, если сайт принимает ввод пользователя и сразу же «отражает» его обратно в ответе, не сохраняя данные на сервере. Это может произойти в многошаговых формах, где данные из одного шага используются для генерации следующего.
Например, форма спрашивает: «Какое ваше любимое блюдо?» Пользователь вводит «пицца», и на следующем шаге появляется вопрос: «Какая ваша любимая пицца?» Но если злоумышленник введёт вместо ответа такой код:
<script>alert('Вредоносный код')</script>
То на следующем шаге вопрос изменится на: «Какая ваша любимая <script>alert('Вредоносный код')</script>?» Если сайт не экранирует ввод, то есть не преобразует специальные символы в безопасный текст, браузер воспримет код как часть страницы и выполнит его.
Для предотвращения таких атак нужно экранировать пользовательский ввод, преобразуя специальные символы в безопасный текст, а также валидировать данные перед их использованием или отображением на странице.
Хранимые (постоянные) XSS
Хранимая XSS-атака происходит, если сайт позволяет пользователям отправлять и сохранять HTML-код — например, в комментариях или профилях пользователей. Если ввод пользователя не экранируется, злоумышленник может вставить в этот код вредоносный JavaScript. После этого любой посетитель сайта, который откроет страницу с этим комментарием, автоматически запустит вредоносный скрипт.
Допустим, на сайте есть форма для комментариев. Злоумышленник оставляет такой комментарий:
<script>alert('Ваши куки украдены!')</script>
Когда другие пользователи зайдут на страницу с этим комментарием, в их браузерах выполнится скрипт. Этот код может не только показывать сообщения, но и красть куки или отправлять данные злоумышленнику.
Схематично такая атака может выглядеть так:
DOM-модели
DOM XSS отличается тем, что весь вредоносный код работает только в браузере пользователя, без передачи данных на сервер и изменения ответа от него. Атака происходит на стороне клиента, когда JavaScript на странице напрямую использует данные, введённые пользователем. Злоумышленник изменяет структуру страницы (DOM), заставляя исходный скрипт выполнять какие-то действия.
Например, на сайте есть код, который использует параметры из URL для отображения данных:
// Получаем значение параметра 'name' из строки запроса URL
const userInput = new URLSearchParams(window.location.search).get('name');
// Вставляем пользовательский ввод внутрь HTML-документа
document.body.innerHTML = `Привет, ${userInput}`;
Если злоумышленник отправит ссылку типа такой:
http://example.com/?name=<script>alert('XSS')</script>
то вредоносный код будет выполнен прямо в браузере жертвы. Скрипт сработает, поскольку код на сайте не экранирует и не проверяет пользовательский ввод, переданный через параметры URL. Кроме этого, используется устаревший и небезопасный метод innerHTML
. Метод напрямую вставляет HTML-код в документ, а это открывает возможность для XSS-атак. Важно избегать innerHTML
и использовать методы, работающие напрямую с DOM-деревом, например textContent
.
Как злоумышленник внедряет вредоносный код
Есть много разных методов, чтобы внедрить вредоносный код на сайт и обойти защитные фильтры. Даже мелкая ошибка в обработке пользовательского ввода может стать входной точкой для атаки.
Вредоносный код может быть внедрён в различных контекстах HTML.
- Внутри тегов:
<input value="XSS">
— код исполняется, если ввод некорректно обработан. - В атрибутах:
<a href="javascript:alert('XSS')">Ссылка</a>
. - В текстовом контексте, если используется метод
innerHTML
.
Обычно для защиты от XSS используются стандартные фильтры, которые автоматически удаляют или экранируют потенциально опасные элементы из пользовательского ввода. Фильтры могут быть настроены на удаление тега <script>
или блокировку известных ключевых слов. Но злоумышленники находят способы обхода фильтров, используя менее очевидные конструкции.
Например, атаку можно провести с помощью тега <img>
. Он не всегда воспринимается как угроза, поскольку его задача — показывать изображения, а не выполнять скрипты. Но злоумышленники могут использовать обработчик событий onerror
, который срабатывает, если изображение не загружается. Вот такая ссылка
http://example.com/?name=<img src=x onerror=alert('XSS')>
вставляет тег <img>
с некорректным src
, из-за чего вызывается обработчик onerror
, выполняющий вредоносный код.
Если фильтр ищет конкретные слова или последовательности, допустим, ключевое слово onerror, злоумышленники могут их замаскировать, то есть использовать обфускацию. Например, так:
<img src="x" onev\u0065nt="alert('XSS')">
Здесь используется Unicode, чтобы скрыть обработчик событий.
Или так: "><script>window['\x61\x6c\x65\x72\x74']('\x58\x53\x53')</script>
Вместо явного вызова alert('XSS')
используются закодированные символы. При вставке такого кода в уязвимый сайт браузер выполнит обфусцированный JavaScript, вызвав тот же alert('XSS')
.
Современные браузеры могут блокировать простейшие XSS-атаки, анализируя их контекст. Например, Content Security Policy (CSP) предотвращает выполнение подозрительных скриптов. Но если сайт уязвим, даже обфусцированный код может сработать.
Чтобы посмотреть, как браузер блокирует вредоносный код или подозрительный скрипт, нужно зайти в инструменты разработчика, перейти во вкладку «Сеть» и посмотреть на все загружаемые ресурсы.
В этом примере статус запроса указывает причину блокировки (заблокировано: другое). Это означает, что браузер или серверная защита (например, Content Security Policy) предотвратили выполнение потенциально опасного кода.
Предотвращение XSS-атак
XSS-атаки могут быть крайне опасны, но при правильной настройке сайта их можно предотвратить. Основное правило: ко всем данным, которые поступают от пользователей, нужно относиться как к потенциальной угрозе. Это касается любой информации — текста в формах, параметров в URL, загружаемых файлов. Защиту нужно настраивать на двух уровнях: на стороне сервера и на стороне клиента.
Защита на стороне сервера
✅ Все данные, которые приходят от пользователей, нужно экранировать или фильтровать перед их отображением. Это предотвращает выполнение вредоносного кода:
- Экранирование в HTML: заменяем < и > на
<
и>
. - Экранирование в JavaScript: избегаем прямой вставки значений и используем безопасный метод
textContent
вместоinnerHTML
.
✅ Если пользователь может вводить HTML-код (например, комментарии), нужно использовать библиотеки для очистки, например DOMPurify. Они автоматически удаляют потенциально опасные элементы.
✅ Важно валидировать данные и настроить строгие критерии для ввода данных — проверять длину текста, формат или тип данных, которые отправляет пользователь. Это помогает исключить некорректные значения ещё до обработки.
✅ Для защиты кук нужно добавить флаг HttpOnly
, чтобы JavaScript не мог получить к ним доступ. А флаг Secure
обеспечит передачу кук только через HTTPS:
res.cookie('sessionId', 'value', { httpOnly: true, secure: true });
✅ Устаревшие версии серверных библиотек могут содержать известные уязвимости. Поэтому важно регулярно обновлять библиотеки, чтобы защитить сайт от атак, использующих эти уязвимости.
Защита на стороне клиента
👉 Настраиваем политику безопасности контента (CSP). CSP — это набор правил, который задаётся для браузера. Эти правила определяют, какие скрипты можно выполнять на сайте, а какие — нет. Настройте CSP-заголовки для запрета inline-скриптов и ограничения доверенных источников для загрузки скриптов.
👉 Не используем JavaScript прямо в HTML, например в атрибутах onclick
, onerror
. Лучше привязывать обработчики событий через JavaScript.
// небезопасно
<button onclick="doSomething()">Click me</button>
// безопасно
button.addEventListener('click', doSomething);
👉 Ограничиваем URI-схемы. Они определяют способ интерпретации содержимого ссылки и могут быть использованы для выполнения вредоносного кода напрямую в браузере, если на сайте плохая защита. Поэтому важно запретить использование URI-схем javascript:, data:, vbscript:
в ссылках или других атрибутах. Это снижает вероятность выполнения вредоносного кода через поддельные ссылки:
// вредоносный код
<a href="javascript:alert('XSS')">Click me</a>
👉 Блокируем подозрительные запросы. Браузеры уже умеют защищаться от некоторых видов атак. Они блокируют подозрительные запросы или кэшируют данные более безопасно. Чтобы усилить защиту, можно включить строгую политику CORS или использовать дополнительные заголовки безопасности.
Как проверить сайт на уязвимости XSS
Проверить сайт на уязвимости XSS можно двумя способами: вручную и с помощью специальных инструментов.
Если хотите самостоятельно проверить, как сайт реагирует на ввод вредоносного кода, то подойдёт ручное тестирование. Вы делаете всё то, о чём мы говорили в статье:
- Пытаетесь вставить скрипт, например
<script>alert('XSS')</script>
, в поля формы или передать его через параметры URL. - Если браузер отобразит всплывающее окно или другой результат выполнения кода, значит, защита на сайте отсутствует или настроена неправильно.
Кроме этого, стоит попробовать ввести нестандартные символы или загрузить файлы с неподходящими расширениями (.exe или .docx) через форму загрузки. Если сайт принимает такие файлы без проверки, это говорит об отсутствии валидации. Такая уязвимость может позволить злоумышленникам загружать вредоносные файлы на сервер.
Ручное тестирование требует времени, но оно помогает понять детали работы сайта и выявить слабые места, которые не всегда видны автоматическим инструментам.Если же времени на ручные проверки нет или нужно быстро проанализировать сайт, то подойдут автоматизированные инструменты — Burp Suite, OWASP ZAP, bWAPP, Acunetix. Они сканируют сайт, проверяют его на наличие XSS и других типов инъекций (injection), анализируют пользовательский ввод, проверяют экранирование и наличие подозрительных ответов, а затем формируют отчёт о найденных уязвимостях.
Автоматизация хороша для общего анализа и быстрого обнаружения проблем, но результаты лучше перепроверить вручную, чтобы убедиться в их точности и лучше разобраться в причинах уязвимости.