Межсайтовый скриптинг и классы уязвимостей на стороне браузера
Что это такое
Межсайтовый скриптинг — это инъекция в страницу: подконтрольное атакующему содержимое отрисовывается как разметка или скрипт, и браузер выполняет его с привилегиями самого сайта — читает 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 и строгий SameSite
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.
- Непроверенные сторонние скрипты — тег имеет тот же доступ, что и ваш код; исправить: держите именованный список и загружайте только с известных источников.
Что читать дальше
- Инъекции и разграничение доступа (07-security/01-web-security-owasp-top-10/01-injection-and-access-control) — серверные классы, за которыми следует эта глава.
- Веб-безопасность (OWASP Top 10) (07-security/01-web-security-owasp-top-10) — трек, который закрывает эта глава.
- Content Security Policy (CSP) (07-security/06-content-security-policy-csp) — политика, ограничивающая ущерб, когда кодирование не срабатывает.
- Управление секретами (07-security/03-secrets-management) — как держать учётные данные, за которыми пойдёт внедрённый скрипт, вне досягаемости.