Безопасность и санитизация

При работе с Markdown-it в JavaScript одной из ключевых задач является предотвращение потенциальных уязвимостей, связанных с XSS (Cross-Site Scripting) и внедрением вредоносного HTML-кода. Markdown-it по умолчанию интерпретирует разметку Markdown в HTML, что делает необходимым контролировать, какие элементы и атрибуты разрешены в итоговом выводе.

Ключевые угрозы:

  1. Встраивание скриптов через HTML-теги<script>, onload, onclick и другие атрибуты могут быть внедрены пользователем.
  2. Встраивание внешних ресурсов<img src="jav * ascript:..."> или <iframe> могут использоваться для выполнения нежелательных действий.
  3. CSS-инъекции – стили через атрибут style могут быть использованы для обхода ограничений отображения или подмены интерфейса.

Markdown-it предоставляет базовый функционал рендеринга Markdown, но для безопасного использования в публичных веб-приложениях необходимо применять фильтры и плагины для санитизации.


Опции безопасного рендеринга

Markdown-it поддерживает набор конфигурационных опций, которые помогают ограничить возможности ввода:

  • html – разрешает или запрещает HTML-теги внутри Markdown.

    const md = require('markdown-it')({ html: false });

    Установка false полностью блокирует произвольный HTML.

  • linkify – автоматическое преобразование URL в ссылки. При включении необходимо дополнительно проверять атрибуты target и rel для предотвращения target="_blank" атак.

  • typographer – преобразует кавычки, тире и т.д. Опасности XSS напрямую не создает, но важно понимать, как рендерятся специальные символы.

Эти базовые опции создают первый уровень защиты, но для полноценной безопасности требуется использовать внешние средства санитизации.


Санитизация HTML

Для безопасного отображения HTML-кода после рендеринга Markdown используется санитизация. Наиболее популярными библиотеками в экосистеме Node.js являются DOMPurify и sanitize-html.

Пример с DOMPurify:

const DOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const purify = DOMPurify(window);

const md = require('markdown-it')();
const unsafeHtml = md.render(userInput);
const safeHtml = purify.sanitize(unsafeHtml, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'ol', 'code', 'pre'],
  ALLOWED_ATTR: ['href', 'title']
});

Важные моменты:

  • Всегда ограничивать список разрешённых тегов и атрибутов.
  • Атрибуты вроде style, class или id могут быть потенциально опасны, особенно если обрабатываются динамически.
  • Внешние ссылки следует принудительно делать безопасными через rel="noopener noreferrer".

Фильтрация ссылок

Markdown часто используется для генерации ссылок. Необходимо проверять URL, чтобы исключить:

  • jav * ascript: схемы
  • data: схемы для внедрения Base64-контента
  • Подозрительные протоколы вроде file: или vb * script:

Пример безопасной фильтрации ссылок:

md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
  const hrefIndex = tokens[idx].attrIndex('href');
  if (hrefIndex >= 0) {
    const url = tokens[idx].attrs[hrefIndex][1];
    if (!url.startsWith('http') && !url.startsWith('https')) {
      tokens[idx].attrs[hrefIndex][1] = '#';
    }
    tokens[idx].attrPush(['rel', 'noopener noreferrer']);
    tokens[idx].attrPush(['target', '_blank']);
  }
  return self.renderToken(tokens, idx, options);
};

Плагины и кастомные правила

Markdown-it поддерживает подключение плагинов и определение кастомных правил рендеринга, что позволяет контролировать и фильтровать контент на уровне токенов:

  • Удаление нежелательных блоков – можно перехватывать токены HTML и полностью их блокировать.
  • Фильтрация изображений – проверка src перед рендерингом позволяет запрещать внешние ресурсы или ограничивать размеры.
  • Разрешение безопасного HTML – можно selectively разрешать только определённые теги с проверкой атрибутов.

Пример кастомного правила для блокировки <iframe>:

md.core.ruler.push('remove_iframe', state => {
  state.tokens = state.tokens.filter(token => {
    if (token.type === 'html_block' && token.content.includes('<iframe')) {
      return false;
    }
    return true;
  });
});

Рекомендации по безопасному использованию

  1. Всегда отключать встроенный HTML (html: false) при отображении контента от пользователей.
  2. Применять библиотеку санитизации после рендеринга Markdown.
  3. Фильтровать все ссылки и ресурсы, добавляя безопасные атрибуты.
  4. Ограничивать разрешённые теги и атрибуты до минимально необходимых для отображения контента.
  5. Использовать кастомные правила Markdown-it для удаления или трансформации потенциально опасных элементов.
  6. Проверять контент в тестовой среде и проводить регулярные проверки XSS-уязвимостей.

Эти практики обеспечивают безопасный вывод Markdown-контента и предотвращают большинство распространённых атак на веб-приложения.