Экранирование пользовательского контента

Экранирование пользовательского контента — ключевой аспект безопасности при работе с текстом, вводимым пользователями, особенно в веб-приложениях, где текст может содержать HTML или Markdown-разметку. Библиотека Marked для JavaScript предоставляет удобные инструменты для обработки Markdown, но требует тщательного контроля ввода для предотвращения XSS-атак и других уязвимостей.


Основные угрозы при обработке Markdown

  1. Внедрение HTML и скриптов Markdown позволяет вставлять HTML-теги напрямую. Без экранирования <script>, <img onerror> или <iframe> могут быть выполнены в браузере пользователя, что представляет серьезную угрозу.

  2. Атрибуты с вредоносным кодом Даже безопасные теги, такие как <a> или <img>, могут содержать опасные атрибуты (href="jav * ascript:...", onmouseo ver="..."), которые активируют выполнение скриптов.

  3. Разделение контента и разметки Markdown создаёт структуру документа, но для безопасности необходимо отделять контент пользователя от разметки, либо корректно экранировать опасные элементы.


Методы экранирования в Marked

1. Встроенные опции безопасности

Marked предоставляет опцию sanitize, которая автоматически фильтрует HTML-теги в Markdown. Пример:

import { marked } from 'marked';

const rawMarkdown = 'Пример <script>alert("XSS")</script>';
const html = marked(rawMarkdown, { sanitize: true });
console.log(html); // Выведет "Пример "

Важно: начиная с версии 1.x, опция sanitize считается устаревшей. Вместо неё рекомендуется использовать внешний санитайзер, например, DOMPurify.


2. Использование кастомных рендереров

Marked позволяет определить собственный рендерер для элементов Markdown:

import { marked } from 'marked';

const renderer = new marked.Renderer();

renderer.link = function(href, title, text) {
    const safeHref = DOMPurify.sanitize(href);
    return `<a href="${safeHref}" title="${title || ''}">${text}</a>`;
};

const markdown = '[Google](jav * ascript:alert("XSS"))';
const html = marked(markdown, { renderer });
console.log(html); // Ссылка безопасна

Этот подход позволяет экранировать опасные атрибуты и контролировать генерацию HTML на уровне каждого элемента.


3. Преобразование HTML в безопасный текст

Если не требуется HTML-разметка, а Markdown нужен только для форматирования, можно использовать функцию экранирования:

function escapeHtml(str) {
    return str
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}

const rawMarkdown = 'Пример <b>текста</b>';
const html = escapeHtml(rawMarkdown);
console.log(html); // Превратит <b> в безопасный текст

Комбинация escapeHtml и Marked без включения HTML-тегов гарантирует, что весь пользовательский контент останется безопасным.


Настройка безопасного парсинга Markdown

Marked позволяет гибко настраивать парсер через опции:

  • gfm: включает расширенный синтаксис GitHub Flavored Markdown, полезен для форматирования таблиц и чекбоксов.
  • headerIds: управление генерацией идентификаторов заголовков; важно для предотвращения конфликтов и XSS через id.
  • mangle: автоматическое преобразование email в безопасные ссылки, защищает от спам-ботов.
  • smartypants: преобразует кавычки и тире, не влияя на безопасность, но улучшает читаемость текста.

Пример безопасного парсинга с DOMPurify:

import DOMPurify from 'dompurify';
import { marked } from 'marked';

const markdown = 'Пример [ссылки](jav * ascript:alert("XSS"))';
const rawHtml = marked(markdown);
const safeHtml = DOMPurify.sanitize(rawHtml);

console.log(safeHtml); // Безопасный HTML для вставки на страницу

Практические рекомендации

  1. Всегда фильтровать HTML при использовании Markdown, введенного пользователями.
  2. Предпочитать безопасные ссылки и изображения через кастомные рендереры.
  3. Использовать проверенные библиотеки (DOMPurify, sanitize-html) для очистки сгенерированного HTML.
  4. Не отключать встроенные механизмы безопасности Marked без веской причины.
  5. Разделять контент и разметку: если HTML не нужен, полностью экранировать пользовательский текст.

Экранирование пользовательского контента — фундамент для безопасного веб-приложения. При работе с Markdown важно комбинировать функции Marked с внешними санитайзерами, контролировать рендеринг ссылок и изображений и никогда не доверять сырому вводу напрямую.