Санитизация URL и протоколов

В контексте работы с Markdown и HTML в экосистеме JavaScript библиотеки Remark и Rehype предоставляют гибкие инструменты для обработки и трансформации контента. Одной из ключевых задач при работе с внешними данными является санитизация URL и протоколов, чтобы предотвратить внедрение вредоносного кода и обеспечить безопасность рендеринга.

Основные угрозы и цели санитизации

При обработке Markdown и HTML URL могут содержать:

  • JavaScript-инъекции через протокол jav * ascript:.
  • Фишинговые ссылки, маскирующие опасные ресурсы под доверенные.
  • Ссылки на небезопасные ресурсы, вызывающие загрузку вредоносного контента.

Целью санитизации является фильтрация или нормализация URL, оставляя только безопасные протоколы (http, https, mailto, tel) и корректные форматы ссылок.

Работа с Remark

Remark работает с Markdown AST (Abstract Syntax Tree) и предоставляет доступ к узлам типа link и image, где хранятся URL. Для санитизации можно использовать плагинную архитектуру:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import { visit } from 'unist-util-visit';

function sanitizeUrls() {
  return (tree) => {
    visit(tree, ['link', 'image'], (node) => {
      const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
      try {
        const url = new URL(node.url, 'http://example.com');
        if (!allowedProtocols.includes(url.protocol)) {
          node.url = '#';
        }
      } catch {
        node.url = '#';
      }
    });
  };
}

const processor = unified()
  .use(remarkParse)
  .use(sanitizeUrls)
  .use(remarkStringify);

const markdown = '[Click me](jav * ascript:alert("XSS"))';
processor.process(markdown).then((file) => {
  console.log(String(file));
});

Ключевые моменты:

  • Используется unist-util-visit для обхода всех ссылок и изображений.
  • URL конструктор обеспечивает корректную проверку протокола.
  • Опасные протоколы заменяются безопасной заглушкой (#).

Работа с Rehype

Rehype работает с HTML AST и позволяет выполнять аналогичную санитизацию на уровне тегов a и img:

import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import { visit } from 'unist-util-visit';

function sanitizeUrlsRehype() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'a' || node.tagName === 'img') {
        const urlAttr = node.tagName === 'a' ? 'href' : 'src';
        if (node.properties && node.properties[urlAttr]) {
          const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
          try {
            const url = new URL(node.properties[urlAttr], 'http://example.com');
            if (!allowedProtocols.includes(url.protocol)) {
              node.properties[urlAttr] = '#';
            }
          } catch {
            node.properties[urlAttr] = '#';
          }
        }
      }
    });
  };
}

const html = '<a href="jav * ascript:alert(1)">Click me</a>';
unified()
  .use(rehypeParse, { fragment: true })
  .use(sanitizeUrlsRehype)
  .use(rehypeStringify)
  .process(html)
  .then((file) => {
    console.log(String(file));
  });

Особенности:

  • Обход узлов выполняется по element, проверяя tagName.
  • URL безопасно проверяется через конструктор URL.
  • Санитизация применяется как к ссылкам, так и к изображениям.

Дополнительные меры безопасности

  • Использование whitelist протоколов предотвращает неожиданные атаки через нестандартные схемы.
  • Нормализация URL позволяет избежать обхода фильтров через экранированные символы.
  • Лимитирование и проверка длины URL помогает защититься от атак типа Buffer Overflow или DoS.

Практические советы

  • Всегда использовать безопасные заглушки вместо удаления элементов, чтобы сохранить структуру документа.
  • Проверять относительные URL в контексте корневого домена.
  • Для сложных сценариев применять библиотеки вроде sanitize-html, интегрируя их в pipeline Remark/Rehype.

Сочетание AST-обхода и строгой фильтрации протоколов обеспечивает надежную защиту контента, позволяя безопасно рендерить Markdown и HTML с внешних источников.