Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Безпека та innerHTML». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`innerHTML` присвоює не текст, а HTML: браузер парсить рядок і одразу виконує все, що в ньому знайде, включно з атрибутами-обробниками `onerror` чи `onclick`.** Якщо туди потрапляє коментар, ім'я або будь-яке інше поле від користувача, зловмисник отримує виконання свого JavaScript у контексті вашого домену, тобто доступ до cookies, токена авторизації та дій від імені користувача. Це і є XSS (Cross-Site Scripting). Для звичайного тексту беріть `textContent`, а якщо HTML справді потрібен, спершу проженіть його через санітайзер на кшталт DOMPurify і продублюйте перевірку на сервері. ```javascript element.textContent = userInput; // безпечно: теги стануть текстом element.innerHTML = DOMPurify.sanitize(html); // якщо HTML таки потрібен ``` **Ключове:** `innerHTML` довіряє рядку як коду, тому дані від користувача або екрануємо через `textContent`, або чистимо санітайзером.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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](https://github.com/cure53/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`, інакше код уже виконався.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.