Пошук уроків, статей та іншого контенту
Навчитеся безпечно обробляти дані користувача й розрізняти валідацію, кодування та санітизацію.
Дані користувача не можна вважати безпечними. Навіть якщо це звичайне ім’я, коментар або пошуковий запит, користувач може ввести:
HTML-код;
JavaScript;
дуже довгий рядок;
текст у неправильному форматі;
значення, яке змінює поведінку сторінки.
Найнебезпечніша помилка у браузері — вставити неперевірені дані в HTML як розмітку. Це може призвести до XSS-атаки — виконання шкідливого коду в контексті вашої сторінки.
Наприклад, такий код небезпечний:
const message = "Текст користувача";
result.innerHTML = message;Якщо замість звичайного тексту користувач введе HTML або JavaScript, браузер спробує обробити цей рядок як розмітку.
Валідація — це перевірка, чи відповідають дані очікуваним правилам.
Наприклад, для поля електронної пошти можна перевірити:
значення не порожнє;
довжина не перевищує допустиму;
у значенні є потрібний формат.
Валідація відповідає на питання:
Чи підходять ці дані для нашої задачі?
Валідація у браузері покращує взаємодію з користувачем. Помилку можна показати одразу, не надсилаючи форму на сервер.
<form id="registrationForm">
<label>
Ім’я:
<input id="name" name="name" type="text" required maxlength="50">
</label>
<label>
Вік:
<input id="age" name="age" type="number" min="18" max="120" required>
</label>
<button type="submit">Зареєструватися</button>
</form>
<p id="message" role="status"></p>
<script>
const form = document.querySelector("#registrationForm");
const nameInput = document.querySelector("#name");
const ageInput = document.querySelector("#age");
const message = document.querySelector("#message");
form.addEventListener("submit", (event) => {
event.preventDefault();
const name = nameInput.value.trim();
const age = Number(ageInput.value);
if (name.length < 2) {
message.textContent = "Ім’я має містити щонайменше 2 символи.";
return;
}
if (!Number.isInteger(age) || age < 18 || age > 120) {
message.textContent = "Вік має бути цілим числом від 18 до 120.";
return;
}
message.textContent = `Дані прийнято. Вітаємо, ${name}!`;
});
</script>У цьому прикладі:
required забороняє залишати поле порожнім;
maxlength обмежує довжину тексту;
min і max обмежують числове значення;
trim() прибирає пробіли на початку та в кінці;
Number.isInteger() перевіряє, що значення є цілим числом;
результат виводиться через textContent, а не через innerHTML.
Клієнтський JavaScript можна вимкнути або обійти. Користувач може надсилати запити безпосередньо до сервера, не використовуючи вашу форму.
Тому всі важливі перевірки потрібно повторювати на сервері:
права доступу;
формат даних;
допустимі значення;
довжину рядків;
унікальність електронної пошти;
правила бізнес-логіки.
Клієнтська валідація — це зручність для користувача. Серверна валідація — обов’язкова частина безпеки.
Кодування — це перетворення даних так, щоб вони безпечно використовувалися в певному контексті.
Різні контексти потребують різного кодування:
HTML-текст;
значення HTML-атрибута;
URL;
JavaScript-рядок;
CSS-значення.
У браузері багато стандартних властивостей автоматично працюють безпечніше за innerHTML.
textContentЯкщо потрібно показати користувацький текст, використовуйте textContent:
const output = document.querySelector("#output");
const userText = "<strong>Це не має стати HTML</strong>";
output.textContent = userText;Браузер покаже символи <strong> як звичайний текст. Тег не буде створено.
innerHTMLinnerHTML читає або змінює HTML-розмітку. Він корисний, коли ви свідомо працюєте з HTML, але небезпечний для неперевірених даних.
const output = document.querySelector("#output");
const userText = "<strong>Небезпечні дані</strong>";
// Не робіть так, якщо userText походить від користувача
output.innerHTML = userText;Розглядайте дані користувача як текст за замовчуванням.
Для побудови інтерфейсу безпечніше створювати елементи та додавати текст через textContent:
const list = document.querySelector("#list");
const products = ["Ноутбук", "Навушники", "Клавіатура"];
for (const product of products) {
const item = document.createElement("li");
item.textContent = product;
list.append(item);
}У цьому випадку назва товару не сприймається як HTML.
Атрибути також можуть бути небезпечним контекстом. Не вставляйте довільний рядок у HTML через шаблонний рядок:
// Не робіть так із даними користувача
element.innerHTML = `<a href="${userUrl}">Відкрити</a>`;Безпечніший варіант — створити елемент і встановити властивості окремо:
const link = document.createElement("a");
link.textContent = "Відкрити";
link.href = userUrl;
link.target = "_blank";
link.rel = "noopener noreferrer";
document.body.append(link);Однак навіть так потрібно перевіряти, чи дозволений сам URL. Наприклад, для посилань на зовнішні ресурси можна дозволити лише протоколи http: та https::
function isAllowedUrl(value) {
try {
const url = new URL(value, window.location.origin);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
const userUrl = "https://example.com";
if (isAllowedUrl(userUrl)) {
const link = document.createElement("a");
link.textContent = "Перейти";
link.href = userUrl;
link.rel = "noopener noreferrer";
document.body.append(link);
}Перевірка протоколу важлива, оскільки не кожен рядок, який виглядає як URL, є безпечним посиланням.
Санітизація — це очищення даних від небезпечних частин із збереженням дозволеного вмісту.
Наприклад, редактор коментарів може дозволяти:
жирний текст;
курсив;
списки;
посилання.
У такому випадку простого textContent може бути недостатньо, адже він покаже HTML-теги як текст. Потрібно очистити HTML і залишити лише дозволені елементи та атрибути.
Санітизація відповідає на питання:
Які частини даних можна залишити, а які потрібно видалити?
Валідація перевіряє, чи відповідають дані правилам.
Кодування змінює представлення даних для конкретного контексту.
Санітизація видаляє небезпечні частини даних.
Ці підходи не є взаємозамінними.
Наприклад, перевірка, що рядок не порожній, не робить HTML безпечним. А кодування HTML не перевіряє, чи відповідає значення бізнес-правилам.
Якщо застосунок не повинен підтримувати HTML від користувача, найкращий варіант — не приймати HTML взагалі та використовувати textContent.
Якщо HTML справді потрібен:
використовуйте спеціалізовану бібліотеку санітизації;
визначте дозволені теги й атрибути;
не намагайтеся написати повноцінний HTML-санітизатор за допомогою кількох регулярних виразів;
повторюйте необхідну обробку на сервері.
Регулярні вирази не є надійним способом аналізу всіх варіантів HTML і браузерної поведінки.
Нижче наведено повний приклад сторінки. Користувач може ввести текст, але він буде показаний лише як текст:
<!doctype html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<title>Безпечний коментар</title>
</head>
<body>
<h1>Залишити коментар</h1>
<form id="commentForm">
<label for="comment">Коментар</label>
<textarea
id="comment"
name="comment"
rows="5"
maxlength="300"
required
></textarea>
<button type="submit">Додати</button>
</form>
<p id="error" role="alert"></p>
<h2>Коментарі</h2>
<ul id="comments"></ul>
<script>
const form = document.querySelector("#commentForm");
const commentInput = document.querySelector("#comment");
const error = document.querySelector("#error");
const comments = document.querySelector("#comments");
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = commentInput.value.trim();
if (text.length < 3) {
error.textContent = "Коментар має містити щонайменше 3 символи.";
return;
}
error.textContent = "";
const item = document.createElement("li");
item.textContent = text;
comments.append(item);
form.reset();
commentInput.focus();
});
</script>
</body>
</html>Спробуйте ввести:
<script>alert("Помилка")</script>У безпечному прикладі цей текст буде показаний на сторінці, але не виконається.
innerHTML для даних користувачаresult.innerHTML = userInput;Замість цього:
result.textContent = userInput;HTML-атрибути required, pattern і maxlength можна обійти. Важливі перевірки мають виконуватися на сервері.
Спроба видалити небезпечні конструкції на кшталт <script> не врахує всі варіанти HTML та контексти. Для HTML використовуйте перевірені спеціалізовані інструменти або не дозволяйте HTML взагалі.
Безпечний спосіб для тексту не гарантує безпеки для URL, CSS або JavaScript-коду. Кожен контекст має власні правила.
maxlengthmaxlength обмежує введення через конкретний елемент форми, але не захищає дані, отримані з іншого джерела. Довжину потрібно перевіряти в JavaScript і на сервері.
Такий код важче перевіряти:
container.innerHTML = `
<div class="card">
<h2>${userName}</h2>
</div>
`;Для динамічних даних краще використовувати createElement, textContent і append.
Під час роботи з даними користувача дотримуйтеся такого порядку:
Визначте, які дані очікуються.
Обмежте довжину та формат.
Перевірте дані на клієнті для зручності користувача.
Повторіть критичні перевірки на сервері.
Виводьте звичайний текст через textContent.
Не використовуйте innerHTML для неперевірених значень.
Якщо HTML необхідний, застосуйте спеціалізовану санітизацію.
Окремо перевіряйте URL, атрибути та інші спеціальні контексти.
Дані користувача завжди потрібно вважати потенційно небезпечними.
Валідація перевіряє формат, довжину та допустимі значення.
Кодування готує дані до безпечного використання в конкретному контексті.
Санітизація очищує HTML або інший вміст від небезпечних частин.
Для звичайного тексту використовуйте textContent.
Не вставляйте неперевірені дані через innerHTML.
Клієнтська валідація не замінює серверну.
Якщо HTML від користувача не потрібен, не дозволяйте його.