Защита от XSS атак

XSS (Cross-Site Scripting) — тип веб-уязвимости, при котором злоумышленник внедряет вредоносный JavaScript-код в страницу приложения. Этот код выполняется в браузере другого пользователя и получает доступ к данным страницы, cookies, токенам авторизации, содержимому форм и API.

Наиболее распространённые последствия:

  • кража сессии пользователя;
  • подмена интерфейса;
  • внедрение фишинговых форм;
  • выполнение действий от имени пользователя;
  • перенаправление на вредоносные сайты;
  • компрометация SPA-приложений.

Пример опасного ввода:

<script>alert('XSS')</script>

Если приложение выводит такой текст без обработки:

element.innerHTML = userInput;

браузер интерпретирует строку как HTML и выполнит <script>.


Типы XSS-атак

Stored XSS

Вредоносный код сохраняется в базе данных и затем отображается другим пользователям.

Пример:

<script>fetch('/steal-cookie')</script>

Сценарий:

  1. злоумышленник публикует комментарий;
  2. комментарий сохраняется в БД;
  3. другие пользователи открывают страницу;
  4. браузер выполняет внедрённый JavaScript.

Reflected XSS

Вредоносный код передаётся через URL или параметры запроса и сразу отображается сервером.

Пример URL:

https://site.com/search?q=<script>alert(1)</script>

Если сервер вставляет параметр q в HTML без фильтрации, код выполняется.


DOM-based XSS

Уязвимость возникает исключительно на стороне клиента.

Опасный пример:

const params = new URLSearchParams(location.search);

document.body.innerHTML = params.get('message');

Параметр URL:

?message=<img src=x oner ror=alert(1)>

Роль Validator.js в защите от XSS

Validator.js предназначен для:

  • валидации строк;
  • очистки пользовательского ввода;
  • нормализации данных;
  • удаления потенциально опасных конструкций.

Библиотека особенно полезна:

  • в формах регистрации;
  • в комментариях;
  • в CMS;
  • в REST API;
  • в административных панелях;
  • в чатах и мессенджерах.

Установка:

npm install validator

Подключение:

const validator = require('validator');

ESM:

import validator from 'validator';

Экранирование HTML через escape()

Главный инструмент защиты от XSS в Validator.js — метод escape().

Он заменяет специальные HTML-символы безопасными HTML-сущностями.


Пример работы escape()

const validator = require('validator');

const input = '<script>alert("XSS")</script>';

const safe = validator.escape(input);

console.log(safe);

Результат:

&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

Браузер больше не воспринимает строку как HTML.


Какие символы экранируются

escape() преобразует:

Символ Результат
< &lt;
> &gt;
" &quot;
' &#x27;
& &amp;
/ &#x2F;

Защита комментариев

Без защиты:

app.post('/comment', (req, res) => {
    const comment = req.body.comment;

    saveComment(comment);
});

Опасный ввод:

<img src=x oner ror=alert(1)>

Защищённый вариант:

app.post('/comment', (req, res) => {
    const comment = validator.escape(req.body.comment);

    saveComment(comment);
});

Очистка данных перед сохранением

Одна из распространённых ошибок — сохранение необработанного HTML в базу данных.

Неправильно:

db.save({
    text: req.body.text
});

Правильно:

db.save({
    text: validator.escape(req.body.text)
});

Такой подход:

  • предотвращает Stored XSS;
  • снижает риск повторного использования вредоносного контента;
  • защищает административные панели.

Комбинирование trim() и escape()

Пользовательский ввод часто содержит:

  • пробелы;
  • табуляции;
  • HTML;
  • управляющие символы.

Комплексная обработка:

const text = validator.escape(
    validator.trim(req.body.text)
);

Пример

Ввод:

    <script>alert(1)</script>

После обработки:

&lt;script&gt;alert(1)&lt;/script&gt;

Использование blacklist()

blacklist() удаляет запрещённые символы.

Синтаксис:

validator.blacklist(str, chars);

Удаление HTML-скобок

const input = '<script>alert(1)</script>';

const cleaned = validator.blacklist(input, '<>');

console.log(cleaned);

Результат:

scriptalert(1)/script

Ограничения blacklist()

Метод не обеспечивает полноценную защиту от XSS.

Опасный пример:

img src=x oner ror=alert(1)

Даже без < и > могут оставаться вредоносные конструкции.

Поэтому:

  • blacklist() используется как дополнительная мера;
  • основной защитой остаётся экранирование.

Белые списки через whitelist()

whitelist() оставляет только разрешённые символы.

Пример:

const username = validator.whitelist(
    req.body.username,
    'a-zA-Z0-9_'
);

Допустимые символы:

  • латинские буквы;
  • цифры;
  • _.

Защита логинов

Опасный ввод:

admin<script>

После обработки:

adminscript

Валидация URL

XSS может использоваться через вредоносные ссылки.

Опасный пример:

jav * ascript:alert(1)

Проверка URL через isURL()

validator.isURL(url);

Небезопасный сценарий

const link = req.body.link;

res.send(`<a href="${link}">Link</a>`);

Вредоносный ввод:

jav * ascript:alert(document.cookie)

Защищённый вариант

const link = req.body.link;

if (!validator.isURL(link, {
    protocols: ['http', 'https'],
    require_protocol: true
})) {
    throw new Error('Invalid URL');
}

Ограничение протоколов

Особенно важно запрещать:

  • jav * ascript:
  • data:
  • vb * script:

Безопасная конфигурация:

validator.isURL(url, {
    protocols: ['https'],
    require_protocol: true
});

Защита email-полей

Иногда email используется внутри HTML.

Опасный пример:

"><script>alert(1)</script>

Проверка:

validator.isEmail(email);

Очистка:

validator.normalizeEmail(email);

Дополнительная защита:

validator.escape(email);

sanitizeInput(): универсальная функция защиты

Практический подход — создание общего санитайзера.


Базовая реализация

function sanitizeInput(value) {
    return validator.escape(
        validator.trim(value)
    );
}

Использование:

const username = sanitizeInput(req.body.username);
const comment = sanitizeInput(req.body.comment);

Защита Express-приложения

Middleware для очистки данных

app.use((req, res, next) => {

    for (const key in req.body) {

        if (typeof req.body[key] === 'string') {

            req.body[key] = validator.escape(
                validator.trim(req.body[key])
            );
        }
    }

    next();
});

Что делает middleware

Механизм:

  1. перебирает поля формы;
  2. ищет строковые значения;
  3. удаляет лишние пробелы;
  4. экранирует HTML;
  5. передаёт безопасные данные дальше.

Защита JSON API

XSS опасен не только для HTML-страниц.

Пример API:

{
    "message": "<script>alert(1)</script>"
}

Frontend может вставить это значение в DOM:

element.innerHTML = data.message;

Поэтому данные желательно очищать ещё на сервере.


Очистка JSON

app.post('/api/message', (req, res) => {

    const message = validator.escape(req.body.message);

    res.json({
        message
    });
});

Проблема двойного экранирования

Иногда данные экранируются несколько раз.

Пример:

validator.escape('&lt;script&gt;');

Результат:

&amp;lt;script&amp;gt;

На странице появляются HTML-сущности вместо текста.


Как избежать проблемы

Правило:

  • данные экранируются один раз;
  • обычно — перед выводом.

Не рекомендуется:

  • многократно применять escape();
  • смешивать разные механизмы экранирования.

Защита шаблонизаторов

Многие шаблонизаторы уже имеют встроенное экранирование.

Например:

  • EJS;
  • Pug;
  • Handlebars;
  • Nunjucks.

Однако опасность возникает при ручном выводе HTML.


Опасный вывод

EJS:

<%- userContent %>

<%- %> отключает экранирование.

Безопасный вариант:

<%= userContent %>

Validator.js и React

React автоматически экранирует строки:

<div>{userInput}</div>

Но опасность появляется при использовании:

dangerouslySetInnerHTML

Опасный пример

<div
    dangerouslySetInnerHTML={{
        __html: userInput
    }}
/>

Предварительная очистка

const safe = validator.escape(userInput);

Validator.js и Vue

Vue безопасен при обычном выводе:

<div>{{ text }}</div>

Но директива v-html опасна:

<div v-html="text"></div>

Требуется очистка:

this.text = validator.escape(input);

Validator.js и Angular

Angular автоматически экранирует значения.

Опасные конструкции:

[innerHTML]

или:

bypassSecurityTrustHtml()

Перед использованием необходимо очищать ввод.


Ограничения Validator.js

Validator.js не является полноценным HTML-санитайзером.

Библиотека:

  • не анализирует DOM;
  • не удаляет сложные HTML-конструкции;
  • не проверяет CSS-инъекции;
  • не фильтрует SVG-атаки.

Когда Validator.js недостаточно

Сложный пример:

<svg onl oad=alert(1)>

Или:

<a href="jav * ascript:alert(1)">

Для полноценной HTML-очистки лучше использовать:

  • DOMPurify;
  • sanitize-html;
  • xss.

Комбинация Validator.js и DOMPurify

Частая архитектура:

Инструмент Назначение
Validator.js Валидация и базовая очистка
DOMPurify Полная HTML-санитизация

Пример

import validator from 'validator';
import DOMPurify from 'dompurify';

const input = validator.trim(req.body.text);

const safe = DOMPurify.sanitize(input);

Контекстная защита

XSS-защита зависит от места вывода данных.

Разные контексты требуют разных механизмов:

Контекст Защита
HTML escape
URL encodeURIComponent
JavaScript JSON.stringify
CSS фильтрация значений
HTML-атрибут экранирование кавычек

Опасность innerHTML

Наиболее распространённая причина XSS:

element.innerHTML = userInput;

Безопаснее:

element.textContent = userInput;

или:

element.innerText = userInput;

Content Security Policy (CSP)

Даже при использовании Validator.js рекомендуется включать CSP.

Пример заголовка:

Content-Security-Policy: default-src 'self'

CSP:

  • блокирует inline-скрипты;
  • ограничивает загрузку JS;
  • снижает последствия XSS.

HttpOnly Cookies

Если XSS всё же произошёл, HttpOnly снижает риск кражи cookies.

Пример:

Set-Cookie: session=abc123; HttpOnly; Secure

JavaScript не сможет прочитать cookie:

document.cookie

Частые ошибки

Использование только frontend-защиты

Неправильно:

validator.escape(input);

только в браузере.

Злоумышленник может отправить запрос напрямую к API.

Проверка должна быть:

  • на frontend;
  • на backend.

Доверие данным из базы

Даже сохранённые данные могут быть опасны.

Stored XSS часто возникает именно через БД.


Очистка не всех полей

Уязвимость может находиться:

  • в имени файла;
  • в описании;
  • в email;
  • в URL;
  • в markdown;
  • в сообщениях чата.

Использование regex вместо санитайзера

Опасный подход:

input.replace(/<script>/g, '');

XSS имеет огромное количество обходов:

<ScRiPt>
<img oner ror=...>
<svg>

Практический пример комплексной защиты

import express from 'express';
import validator from 'validator';

const app = express();

app.use(express.json());

function sanitize(value) {

    return validator.escape(
        validator.trim(value)
    );
}

app.post('/comment', (req, res) => {

    const username = sanitize(req.body.username);
    const text = sanitize(req.body.text);

    if (!validator.isLength(username, {
        min: 3,
        max: 30
    })) {

        return res.status(400).json({
            error: 'Invalid username'
        });
    }

    res.json({
        username,
        text
    });
});

Многоуровневая защита от XSS

Надёжная архитектура строится из нескольких уровней:

  1. валидация данных;
  2. экранирование вывода;
  3. санитизация HTML;
  4. CSP;
  5. HttpOnly cookies;
  6. отказ от innerHTML;
  7. проверка URL;
  8. серверная фильтрация;
  9. безопасные шаблонизаторы;
  10. аудит пользовательского контента.

Validator.js является важным элементом этой системы, но максимальная защита достигается только при комплексном подходе.