Skip to main content

Безпека та innerHTML

innerHTML небезпечний з даними користувача тому, що браузер інтерпретує присвоєний рядок як HTML-код і виконує його. Разом з розміткою виконуються вбудовані скрипти та атрибути-обробники, а це прямий шлях до XSS-вразливості (Cross-Site Scripting).

Теорія

TL;DR

  • innerHTML не «вставляє текст», він парсить рядок як HTML і будує з нього DOM.
  • Теги та атрибути-обробники (onerror, onclick, onload) спрацьовують, тому рядок від користувача фактично стає виконуваним кодом.
  • Це XSS: код зловмисника працює в контексті вашого сайту, з вашими cookies і вашою сесією.
  • Для тексту використовуйте textContent або innerText, вони екранують HTML і перетворюють теги на звичайні символи.
  • Якщо HTML справді потрібен, чистіть його санітайзером (DOMPurify) і валідуйте дані ще й на сервері.

Швидкий приклад

javascript
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.

Якщо треба показати лише текст, використовуйте:

javascript
element.textContent = userInput;

або

javascript
element.innerText = userInput;

Вони екранують HTML, перетворюючи теги на звичайний текст. Так само безпечно створювати вузли через document.createTextNode() і призначати атрибути через setAttribute().

2. Якщо HTML таки потрібен, застосуйте санітайзер.

Коли розмітку треба відобразити (наприклад, вона прийшла від модератора чи з перевіреного джерела), проженіть текст через бібліотеку очищення HTML, наприклад DOMPurify:

javascript
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, інакше код уже виконався.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.