Безпека та innerHTML
innerHTML небезпечний з даними користувача тому, що браузер інтерпретує присвоєний рядок як HTML-код і виконує його. Разом з розміткою виконуються вбудовані скрипти та атрибути-обробники, а це прямий шлях до XSS-вразливості (Cross-Site Scripting).
Теорія
TL;DR
innerHTMLне «вставляє текст», він парсить рядок як HTML і будує з нього DOM.- Теги та атрибути-обробники (
onerror,onclick,onload) спрацьовують, тому рядок від користувача фактично стає виконуваним кодом. - Це XSS: код зловмисника працює в контексті вашого сайту, з вашими cookies і вашою сесією.
- Для тексту використовуйте
textContentабоinnerText, вони екранують HTML і перетворюють теги на звичайні символи. - Якщо HTML справді потрібен, чистіть його санітайзером (DOMPurify) і валідуйте дані ще й на сервері.
Швидкий приклад
const comment = '<img src=x onerror="alert(`Hacked!`)">';
// Небезпечно: браузер розпарсить тег і виконає onerror
document.querySelector('#comments').innerHTML = comment;
// Безпечно: рядок залишиться звичайним текстом
document.querySelector('#comments').textContent = comment;Що саме відбувається
Коли ви присвоюєте щось у innerHTML, браузер інтерпретує рядок як HTML-код і виконує все, що в ньому є: теги <script>, атрибути-події (onerror, onclick) і навіть вбудований JS.
Для прикладу вище результат такий:
- на сторінці з'явиться зображення, яке не завантажиться;
- спрацює обробник
onerrorі запуститьсяalert('Hacked!'); - замість нешкідливого
alertзловмисник міг би відправити cookies вашого користувача на свій сервер.
Тобто проблема не в конкретному теґу <img>, а в тому, що рядок узагалі потрапив у HTML-парсер. Варіантів корисного навантаження десятки: <svg onload=...>, <iframe src="javascript:...">, <body onpageshow=...> тощо. Фільтрувати їх вручну по чорному списку марно.
Що таке XSS
XSS (Cross-Site Scripting) це тип атаки, коли зловмисник впроваджує JavaScript-код на вашу сторінку, а браузер виконує його в контексті вашого сайту. Браузер не розрізняє «ваш» і «чужий» скрипт: обидва мають однакові права.
Це дає атакувальнику можливість:
- вкрасти cookies, у тому числі токен авторизації;
- підмінити вміст сторінки;
- додати фейкові форми для крадіжки даних;
- виконувати дії від імені користувача, наприклад оформити замовлення чи надіслати повідомлення.
XSS буває не лише відображеним (значення з URL одразу потрапляє на сторінку), а й збереженим: шкідливий рядок лягає в базу разом із коментарем і виконується щоразу, коли сторінку відкриває будь-хто інший. Саме збережений XSS найнебезпечніший.
Як захиститися
1. Ніколи не вставляйте ввід користувача через innerHTML.
Якщо треба показати лише текст, використовуйте:
element.textContent = userInput;або
element.innerText = userInput;Вони екранують HTML, перетворюючи теги на звичайний текст. Так само безпечно створювати вузли через document.createTextNode() і призначати атрибути через setAttribute().
2. Якщо HTML таки потрібен, застосуйте санітайзер.
Коли розмітку треба відобразити (наприклад, вона прийшла від модератора чи з перевіреного джерела), проженіть текст через бібліотеку очищення HTML, наприклад DOMPurify:
import DOMPurify from 'dompurify';
const safeHTML = DOMPurify.sanitize(userInput);
element.innerHTML = safeHTML;Санітайзер працює за білим списком тегів і атрибутів, тому вирізає і <script>, і будь-які on*-обробники, і javascript:-посилання.
3. Валідуйте та екрануйте дані й на сервері.
Фільтруйте ввід, особливо у формах і коментарях. Клієнтська перевірка не захищає: запит можна надіслати повз ваш інтерфейс. Додатково ввімкніть Content Security Policy, це прибирає інлайнові скрипти як клас.
Підсумкова таблиця
| Проблема | Причина | Безпечне рішення |
|---|---|---|
innerHTML виконує код | Вставлений HTML парситься і запускається браузером | Використовуйте textContent |
Користувач може впровадити <script> або onerror | Браузер довіряє вмісту innerHTML | Використовуйте DOMPurify або серверне очищення |
| XSS дає повний контроль над сторінкою | Атака виконується в контексті вашого домену | Перевіряйте та екрануйте всі дані користувача |
Типові помилки
- Вважати, що
innerHTMLпросто «показує текст». Він будує DOM, і будь-який тег у рядку стане справжнім елементом. - Захищатися чорним списком: вирізати підрядок
<script>регулярним виразом. Обходиться тривіально черезonerror,onload,javascript:чи різний регістр і кодування. - Плутати
innerTextзinnerHTMLв одному коді. Одна забута властивість у шаблоні відкриває дірку в усьому застосунку. - Покладатися лише на клієнтську валідацію. Дані можуть прийти прямо в API повз форму, тому очищення потрібне й на сервері.
- Довіряти «своїм» даним. Ім'я користувача, назва файлу, значення з query-параметра чи відповідь стороннього API це все ще недовірений ввід.
- Санітизувати після присвоєння. Чистити треба рядок перед тим, як він потрапить у
innerHTML, інакше код уже виконався.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.