Типы XSS (кратко) - Reflected (отражённый): полезная нагрузка приходит в запросе (query, form) и сразу возвращается в ответ сервера. Выполняется при переходе по вредной ссылке. - Stored (сохранённый, persistent): вредоносный скрипт сохраняется на сервере (комментарий, профиль, БД) и выполняется у всех, кто просматривает страницу. - DOM-based: уязвимость реализована полностью на клиенте — данные из DOM/URL/вводa попадают в "sink" (например, innerHTML) и выполняются; сервер может не участвовать. Анализ фрагмента document.getElementById('out').innerHTML = userInput - Проблема: запись в innerHTML вставляет строку как HTML, значит любой HTML/JS из userInput выполнится — это XSS. Тип чаще всего — DOM-based, если userInput берётся из location.hash, location.search, document.referrer или из поля ввода; может быть и reflected/stored, если значение приходит с сервера. - Примеры полезной нагрузки: \alert(1)\, \, \, атрибуты onmouseover и т.п. Все они будут исполнены при вставке через innerHTML. Способы защиты (рекомендации, defense-in-depth) 1) Не вставлять непроверенный HTML — использовать textContent / innerText: document.getElementById('out').textContent = userInput; (вставляет как текст, HTML не парсится) 2) Экранировать/энкодить при вставке в HTML: function escapeHtml(s) { return s.replace(/&/g,'&').replace(/</g,'<') .replace(/>/g,'>').replace(/"/g,'"') .replace(/'/g,'''); } element.innerHTML = escapeHtml(userInput); (использовать, если нужно вставить в HTML-контекст вручную) 3) Использовать проверенные библиотеки-сантайзеры (whitelist) при необходимости разрешать ограничённый HTML, например DOMPurify: element.innerHTML = DOMPurify.sanitize(userInput); (конфигурировать белый список тегов/атрибутов; держать библиотеку обновлённой) 4) Применять CSP (Content Security Policy) как уменьшение последствий: Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-...'; (запретить 'unsafe-inline' и javascript: URI; использовать nonce или хэши для допустимых скриптов) 5) Валидировать и очищать ввод на сервере (whitelist), особенно для данных, сохраняемых в БД (stored XSS). Клиентская санитация — недостаточна сама по себе. 6) Избегать опасных API: innerHTML, document.write, eval, setAttribute с inline‑событиями, insertAdjacentHTML — заменять безопасными альтернативами (textContent, createTextNode, element.appendChild). 7) Контекстно корректное кодирование: - HTML-энкодинг для HTML-текста, - Attribute-энкодинг при вставке в атрибуты, - URL-энкодинг для вставки в href/src (проверять протокол — запретить javascript:), - JS-энкодинг при генерации JS-литералов. 8) Безопасные cookie/заголовки: HttpOnly, Secure, SameSite — уменьшают ущерб от кражи сессий. Короткая рекомендация для вашего кода - Если userInput должен быть текстом: заменить innerHTML на textContent. - Если нужен ограниченный HTML — обязательно применять DOMPurify.sanitize или другой надёжный санитайзер + CSP. - И всегда валидировать/энкодить на сервере и избегать доверия к данным из запроса/URL.
- Reflected (отражённый): полезная нагрузка приходит в запросе (query, form) и сразу возвращается в ответ сервера. Выполняется при переходе по вредной ссылке.
- Stored (сохранённый, persistent): вредоносный скрипт сохраняется на сервере (комментарий, профиль, БД) и выполняется у всех, кто просматривает страницу.
- DOM-based: уязвимость реализована полностью на клиенте — данные из DOM/URL/вводa попадают в "sink" (например, innerHTML) и выполняются; сервер может не участвовать.
Анализ фрагмента
document.getElementById('out').innerHTML = userInput
- Проблема: запись в innerHTML вставляет строку как HTML, значит любой HTML/JS из userInput выполнится — это XSS. Тип чаще всего — DOM-based, если userInput берётся из location.hash, location.search, document.referrer или из поля ввода; может быть и reflected/stored, если значение приходит с сервера.
- Примеры полезной нагрузки: \alert(1)\, \
Способы защиты (рекомендации, defense-in-depth)
1) Не вставлять непроверенный HTML — использовать textContent / innerText:
document.getElementById('out').textContent = userInput;
(вставляет как текст, HTML не парсится)
2) Экранировать/энкодить при вставке в HTML:
function escapeHtml(s) {
return s.replace(/&/g,'&').replace(/</g,'<')
.replace(/>/g,'>').replace(/"/g,'"')
.replace(/'/g,''');
}
element.innerHTML = escapeHtml(userInput);
(использовать, если нужно вставить в HTML-контекст вручную)
3) Использовать проверенные библиотеки-сантайзеры (whitelist) при необходимости разрешать ограничённый HTML, например DOMPurify:
element.innerHTML = DOMPurify.sanitize(userInput);
(конфигурировать белый список тегов/атрибутов; держать библиотеку обновлённой)
4) Применять CSP (Content Security Policy) как уменьшение последствий:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-...';
(запретить 'unsafe-inline' и javascript: URI; использовать nonce или хэши для допустимых скриптов)
5) Валидировать и очищать ввод на сервере (whitelist), особенно для данных, сохраняемых в БД (stored XSS). Клиентская санитация — недостаточна сама по себе.
6) Избегать опасных API: innerHTML, document.write, eval, setAttribute с inline‑событиями, insertAdjacentHTML — заменять безопасными альтернативами (textContent, createTextNode, element.appendChild).
7) Контекстно корректное кодирование:
- HTML-энкодинг для HTML-текста,
- Attribute-энкодинг при вставке в атрибуты,
- URL-энкодинг для вставки в href/src (проверять протокол — запретить javascript:),
- JS-энкодинг при генерации JS-литералов.
8) Безопасные cookie/заголовки: HttpOnly, Secure, SameSite — уменьшают ущерб от кражи сессий.
Короткая рекомендация для вашего кода
- Если userInput должен быть текстом: заменить innerHTML на textContent.
- Если нужен ограниченный HTML — обязательно применять DOMPurify.sanitize или другой надёжный санитайзер + CSP.
- И всегда валидировать/энкодить на сервере и избегать доверия к данным из запроса/URL.