Межсайтовый скриптинг и классы уязвимостей на стороне браузера

Что это такое

Межсайтовый скриптинг — это инъекция в страницу: подконтрольное атакующему содержимое отрисовывается как разметка или скрипт, и браузер выполняет его с привилегиями самого сайта — читает cookie, делает запросы от имени пользователя, переписывает страницу. Соседние классы включают подделку межсайтовых запросов (CSRF), небезопасную обработку postMessage и риски цепочки поставок сторонних скриптов.

Почему это важно

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

Ключевые идеи

Экранируйте в точке вывода, по контексту

Правильное кодирование зависит от того, куда попадает значение: текст HTML, атрибут, URL, JavaScript или CSS. JSX в React экранирует текст по умолчанию, поэтому опасность сидит в «чёрных ходах».

// Неверно: отрисовывает сырой HTML
<div dangerouslySetInnerHTML={{ __html: userComment }} />

// Неверно: значение попадает в URL без кодирования
<a href={`/search?q=${query}`}>Search</a>

// Верно: пусть фреймворк экранирует текст, а значение кодируется под назначение
<a href={`/search?q=${encodeURIComponent(query)}`}>Search</a>

Там где богатый текст действительно нужен, санируйте его серверной библиотекой с allow-list, а не регулярным выражением.

Используйте безопасные DOM-API, а не собранный строками HTML

Присваивание в innerHTML разбирает разметку. Текстовые узлы, textContent и привязки фреймворка трактуют значение как данные, чем оно и является.

Cookie с HttpOnly не читается из JavaScript, поэтому удачная инъекция скрипта не украдёт сессию. SameSite ограничивает межсайтовые запросы, на которых строится подделка запросов.

Set-Cookie: session=...; Secure; HttpOnly; SameSite=Lax; Path=/

Lax блокирует большинство межсайтовых POST, сохраняя работу обычной навигации верхнего уровня.

Считайте сторонние скрипты кодом, который вы исполняете

Тег аналитики или виджет чата имеет полный доступ к странице, включая форму, в которую печатает пользователь (о средствах контроля — в записях об управлении секретами и Content Security Policy). Загружайте только с именованных источников и держите список того, что загружено и зачем.

Ограничьте возможности скрипта политикой

Content Security Policy — это подстраховка: она ограничивает источники загрузки скриптов и блокирует встроенный скрипт, поэтому инъекция, пережившая кодирование вывода, часто всё равно не выполняется.

Практика

Проверьте аудитом каждую вставку сырого HTML

Перечислите все места в кодовой базе, которые отрисовывают недоверенное содержимое как разметку — innerHTML, prop сырого HTML во фреймворке, шаблон с неэкранированной переменной, — и исправьте или обоснуйте каждое.

Готово, когда: у каждой оставшейся сырой вставки есть серверная санитизация и комментарий, объясняющий, зачем она.

Проверьте одно сохранённое поле от начала до конца

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

Готово, когда: строка везде выглядит как текст, — либо вы можете назвать конкретную страницу и шаблон, которым нужно экранирование.

Частые ошибки

  • Отрисовка пользовательского содержимого как HTML — скрипт выполняется с привилегиями сайта; исправить: экранируйте по контексту или санируйте allow-list на сервере.
  • Санитизация регулярным выражением — экранирование разметки печально известно своей неподатливостью шаблонам; исправить: используйте поддерживаемый санитайзер.
  • Нет кодирования вывода для не-HTML-контекста — атрибуту или URL нужно другое кодирование, чем тексту; исправить: кодируйте под назначение.
  • Сессионные cookie, читаемые JavaScript — одна инъекция крадёт сессию; исправить: Secure, HttpOnly и SameSite.
  • Непроверенные сторонние скрипты — тег имеет тот же доступ, что и ваш код; исправить: держите именованный список и загружайте только с известных источников.

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