Экранирование и санитизация ввода

Экранирование (escaping) преобразует специальные символы в безопасные представления для конкретного контекста (HTML, SQL, URL); санитизация (sanitization) удаляет или заменяет опасные части ввода. Вместе они предотвращают инъекции и XSS.

Зачем нужно

Пользовательский ввод — основной вектор атак: XSS, SQLi, Command Injection, Path Traversal. Правило: данные от пользователя никогда не должны интерпретироваться как код или команды. Контекст экранирования важен: HTML-экранирование не защитит от SQLi.

Где используется

  • HTML-вывод: предотвращение XSS при рендеринге данных пользователя
  • SQL-запросы: параметризация (экранирование через driver)
  • URL-параметры: encoding при формировании ссылок
  • Файловые пути: предотвращение Path Traversal
  • Shell-команды: ограничение или полный отказ от exec с пользовательским вводом

Основной контент

HTML escaping (XSS prevention)

// Встроенный в большинство шаблонизаторов
// Handlebars: {{value}} — автоэкранирование, {{{value}}} — без (опасно!)
// React: JSX автоматически экранирует {value}

// Ручное HTML-экранирование (если нужно)
function escapeHtml(str) {
  const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
  return str.replace(/[&<>"']/g, ch => map[ch]);
}

// Для разрешённого HTML (комментарии, rich text) — только DOMPurify
const DOMPurify = require('isomorphic-dompurify');
const safeHtml = DOMPurify.sanitize(userHtml, {
  ALLOWED_TAGS: ['p', 'b', 'i', 'a', 'ul', 'li'],
  ALLOWED_ATTR: ['href'],
});

URL encoding

// Кодирование параметров URL
const query = encodeURIComponent(userInput);
const url = `https://api.example.com/search?q=${query}`;

// URLSearchParams — безопасный способ построения query string
const params = new URLSearchParams({ q: userInput, page: 1 });
const url = `https://api.example.com/search?${params}`;

Path Traversal prevention

const path = require('path');

function safeFilePath(baseDir, userInput) {
  // Нормализуем путь
  const resolved = path.resolve(baseDir, userInput);

  // Проверяем, что файл внутри базовой директории
  if (!resolved.startsWith(path.resolve(baseDir))) {
    throw new Error('Path traversal detected');
  }

  return resolved;
}

// Безопасно:
const filePath = safeFilePath('/uploads', req.params.filename);
// Атака "../../etc/passwd" будет отклонена

Input validation с Zod

const { z } = require('zod');

const UserSchema = z.object({
  name: z.string.min(1).max(100).trim(),
  email: z.string.email.toLowerCase(),
  age: z.number.int.min(0).max(150),
  website: z.string.url.optional,
});

app.post('/api/users', (req, res) => {
  const result = UserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.issues });
  }
  const validated = result.data; // Типобезопасные данные
});

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

  • Экранирование только на фронтенде — бэкенд должен делать то же самое
  • Использование innerHTML = userInput вместо textContent — XSS
  • Самописное HTML-экранирование — легко пропустить edge-case. Используйте DOMPurify
  • Санитизация только при вводе, но не при выводе — данные могут прийти из других источников

Связанные темы

Ресурсы