
Ruby HTML and CSS sanitizer.
Sanitize — это санитайзер HTML и CSS, основанный на списке разрешённых элементов (allowlist). Он удаляет из строки весь HTML и/или CSS, кроме тех элементов, атрибутов и свойств, которые вы разрешили.
Используя простой синтаксис конфигурации, вы можете указать Sanitize, какие HTML-элементы, какие атрибуты внутри этих элементов и даже какие URL-протоколы в атрибутах, содержащих URL, разрешить. Вы также можете разрешить определённые CSS-свойства, @-правила и URL-протоколы в элементах или атрибутах, содержащих CSS. Любой HTML или CSS, который вы явно не разрешили, будет удалён.
Sanitize основан на парсере HTML5 Nokogiri, который разбирает HTML так же, как современные браузеры, и на Crass, который разбирает CSS так же, как современные браузеры. Пока ваша конфигурация списка разрешённых допускает только безопасную разметку и CSS, даже самый некорректный или вредоносный ввод будет преобразован в безопасный вывод.
gem install sanitize
## Быстрый старт```ruby
require 'sanitize'
# Clean up an HTML fragment using Sanitize's permissive but safe Relaxed config.
# This also sanitizes any CSS in `<style>` elements or `style` attributes.
Sanitize.fragment(html, Sanitize::Config::RELAXED)
# Clean up an HTML document using the Relaxed config.
Sanitize.document(html, Sanitize::Config::RELAXED)
# Clean up a standalone CSS stylesheet using the Relaxed config.
Sanitize::CSS.stylesheet(css, Sanitize::Config::RELAXED)
# Clean up some CSS properties using the Relaxed config.
Sanitize::CSS.properties(css, Sanitize::Config::RELAXED)
Sanitize может очищать следующие типы входных данных:
<style>style[!WARNING]
Sanitize не может полностью очистить содержимое элементов
<math>или<svg>. Элементы MathML и SVG являются внешними элементами, которые не подчиняются обычным правилам разбора HTML.По умолчанию Sanitize удаляет все элементы MathML и SVG. Если вы добавите элементы MathML или SVG в настраиваемый список разрешённых элементов, вы можете создать уязвимость в безопасности вашего приложения.
Фрагмент — это часть HTML, которая не содержит корневого элемента <html>.
Если вы не укажете никаких параметров конфигурации, Sanitize по умолчанию использует самые строгие настройки, а это означает, что он удалит весь HTML и оставит только безопасный текст.```ruby
html = 'foo
'
Sanitize.fragment(html)
Чтобы сохранить определенные элементы, добавьте их в список разрешенных элементов.```ruby
Sanitize.fragment(html, elements: ['b'])
# => "<b>foo</b>"
При санитизации документа элемент <html> должен быть включён в белый список. Вы также можете установить :allow_doctype в значение true, чтобы разрешить правильно сформированные определения типа документа.```ruby
html = %[
]
Sanitize.document(html, allow_doctype: true, elements: ['html'] )
### CSS в HTML
Чтобы очистить CSS во фрагменте или документе HTML, сначала добавьте в белый список элемент `<style>` и/или атрибут `style`. Затем добавьте в белый список свойства CSS, правила `@` и протоколы URL, которые вы хотите разрешить. Вы также можете выбрать, разрешать ли CSS-комментарии или хаки для совместимости с браузерами.```ruby
html = %[
<style>
div { color: green; width: 1024px; }
</style>
<div style="height: 100px; width: 100px;"></div>
<p>hello!</p>
]
Sanitize.fragment(html,
elements: ['div', 'style'],
attributes: {'div' => ['style']},
css: {
properties: ['width']
}
)
#=> %[
# <style>
# div { width: 1024px; }
# </style>
#
# <div style=" width: 100px;"></div>
# hello!
# ]
Sanitize без труда очистит автономную таблицу стилей CSS или строку свойств без необходимости вызова HTML-парсера.
[!WARNING] Это следует использовать только для очистки CSS, предназначенного для применения в автономном CSS-файле. Чтобы безопасно очистить CSS для использования в контексте HTML, например в элементе
<style>или атрибутеstyle, необходимо использоватьSanitize.document()илиSanitize.fragment()с соответствующим белым списком, как описано в предыдущих разделах. Невыполнение этого требования может сделать возможными атаки с помощью HTML-инъекций.```ruby css = %[ @import url(evil.css);
a { text-decoration: none; }
a:hover { left: expression(alert('xss!')); text-decoration: underline; } ]
Sanitize::CSS.stylesheet(css, Sanitize::Config::RELAXED)
Sanitize::CSS.properties(%[ left: expression(alert('xss!')); text-decoration: underline; ], Sanitize::Config::RELAXED)
## Configuration
В дополнение к сверхбезопасным настройкам по умолчанию Sanitize включает три другие встроенные конфигурации, которые можно использовать сразу из коробки или адаптировать под свои нужды.
### Sanitize::Config::RESTRICTED
Разрешает только очень простую встроенную разметку. Никаких ссылок, изображений или блочных элементов.```ruby
Sanitize.fragment(html, Sanitize::Config::RESTRICTED)
# => "<b>foo</b>"
Позволяет разнообразную разметку, включая элементы форматирования, ссылки и списки.
Изображения и таблицы не разрешены, ссылки ограничены протоколами FTP, HTTP, HTTPS и mailto, а ко всем ссылкам добавляется атрибут rel="nofollow" для снижения SEO-спама.```ruby
Sanitize.fragment(html, Sanitize::Config::BASIC)
### Sanitize::Config::RELAXED
Позволяет использовать ещё более широкий набор разметки, включая изображения и таблицы, а также безопасный CSS. Ссылки по-прежнему ограничены протоколами FTP, HTTP, HTTPS и mailto, а изображения — HTTP и HTTPS. В этом режиме `rel="nofollow"` не добавляется к ссылкам.```ruby
Sanitize.fragment(html, Sanitize::Config::RELAXED)
# => '<b><a href="http://foo.com/">foo</a></b><img src="https://raw.githubusercontent.com/rgrove/sanitize/HEAD/bar.jpg">'
Если встроенные режимы не соответствуют вашим требованиям, вы можете легко задать собственную конфигурацию:```ruby Sanitize.fragment(html, elements: ['a', 'span'],
attributes: { 'a' => ['href', 'title'], 'span' => ['class'] },
protocols: { 'a' => {'href' => ['http', 'https', 'mailto']} } )
Вы также можете начать с одной из встроенных конфигураций Sanitize, а затем настроить её под свои нужды.
Встроенные конфигурации глубоко заморожены, чтобы предотвратить их изменение (случайное или злонамеренное). Чтобы настроить встроенную конфигурацию, создайте новую копию с помощью `Sanitize::Config.merge()`, например:```ruby
# Create a customized copy of the Basic config, adding <div> and <table> to the
# existing allowlisted elements.
Sanitize.fragment(html, Sanitize::Config.merge(Sanitize::Config::BASIC,
elements: Sanitize::Config::BASIC[:elements] + ['div', 'table'],
remove_contents: true
))
В приведённом примере элементы <div> и <table> добавляются в копию существующего списка элементов в Sanitize::Config::BASIC. Если же вы хотите полностью перезаписать массив элементов собственным набором, можно опустить операцию +:```ruby
Sanitize.fragment(html, Sanitize::Config.merge(Sanitize::Config::BASIC, elements: ['div', 'table'], remove_contents: true ))
### Config Settings
#### :add_attributes (Hash)
Атрибуты для добавления к конкретным элементам. Если атрибут уже существует, он будет заменён значением, указанным здесь. Указывайте все имена элементов и атрибуты в нижнем регистре.```ruby
add_attributes: {
'a' => {'rel' => 'nofollow'}
}
Следует ли разрешать HTML-комментарии. Разрешать комментарии настоятельно не рекомендуется, поскольку IE допускает выполнение сценариев внутри условных комментариев. Значение по умолчанию: false.
Следует ли разрешать корректные объявления типа документа HTML, такие как "", при санитизации документа. Этот параметр игнорируется при санитизации фрагментов. Значение по умолчанию: false.
Разрешённые атрибуты для конкретных элементов. Указывайте все имена элементов и атрибуты в нижнем регистре.```ruby attributes: { 'a' => ['href', 'title'], 'blockquote' => ['cite'], 'img' => ['alt', 'src', 'title'] }
Если вы хотите разрешить определённые атрибуты для всех элементов, используйте символ `:all` вместо имени элемента.```ruby
# Allow the class attribute on all elements.
attributes: {
:all => ['class'],
'a' => ['href', 'title']
}
Чтобы разрешить произвольные HTML5-атрибуты data-*, используйте символ :data вместо имени атрибута.```ruby
attributes: { 'div' => [:data] }
#### :css (Hash)
Хэш следующих параметров конфигурации CSS, используемых при санитизации CSS (автономно или встроенного в HTML).
##### :css => :allow_comments (boolean)
Разрешать ли CSS-комментарии. Значение по умолчанию — `false`.
##### :css => :allow_hacks (boolean)
Разрешать ли хаки для совместимости с браузерами, такие как хаки IE `*` и `_`. Обычно они безвредны, но технически приводят к невалидному CSS. По умолчанию — `false`.
##### :css => :at_rules (Array or Set)
Имена CSS [at-rules][at-rules], которые разрешается использовать без связанных блоков, например `import` или `charset`. Имена должны быть указаны в нижнем регистре.
[at-rules]:https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule
##### :css => :at_rules_with_properties (Array or Set)
Имена CSS [at-rules][at-rules], которые разрешается использовать со связанными блоками, содержащими CSS-свойства. К этой категории относятся такие at-rules, как `font-face` и `page`. Имена должны быть указаны в нижнем регистре.
##### :css => :at_rules_with_styles (Array or Set)
Имена CSS [at-rules][at-rules], которые разрешается использовать со связанными блоками, содержащими правила стилей. К этой категории относятся такие at-rules, как `media` и `keyframes`. Имена должны быть указаны в нижнем регистре.
##### :css => :import_url_validator
Это `Proc` (или другой вызываемый объект), который будет вызван и получит URL, указанный для любого `@import` [at-rules][at-rules].
С помощью этого можно ограничить, что может быть импортировано, например, следующим образом можно ограничить `@import` URL-адресами Google Fonts:```ruby
Proc.new { |url| url.start_with?("https://fonts.googleapis.com") }
Список имён CSS-свойств, которые разрешены. Имена следует указывать в нижнем регистре.
URL-протоколы, разрешённые в CSS-URL. Должны быть указаны в нижнем регистре.
Если вы хотите разрешить использование относительных URL без протокола, включите символ :relative в массив протоколов.
Массив имён HTML-элементов, которые разрешены. Указывайте все имена в нижнем регистре. Любые элементы, отсутствующие в этом массиве, будут удалены.```ruby elements: %w[ a abbr b blockquote br cite code dd dfn dl dt em i kbd li mark ol p pre q s samp small strike strong sub sup time u ul var ]
> [!WARNING]
>
> Sanitize не может полностью очистить содержимое элементов `<math>` или `<svg>`. Элементы MathML и SVG являются [внешними элементами](https://html.spec.whatwg.org/multipage/syntax.html#foreign-elements), которые не следуют обычным правилам синтаксического анализа HTML.
>
> По умолчанию Sanitize удаляет все элементы MathML и SVG. Если вы добавите элементы MathML или SVG в пользовательский список разрешённых элементов, вы должны предполагать, что любое содержимое внутри них будет разрешено, даже если это содержимое в противном случае было бы удалено или экранировано Sanitize. Это может создать уязвимость в безопасности вашего приложения.
> [!NOTE]
>
> Sanitize всегда удаляет элементы `<noscript>` и их содержимое, даже если `noscript` находится в списке разрешённых элементов.
>
> Это связано с тем, что содержимое элемента `<noscript>` по-разному анализируется в браузерах в зависимости от того, включены ли сценарии. Поскольку Nokogiri не поддерживает сценарии, он всегда анализирует элементы `<noscript>` так, как если бы сценарии были отключены. Это приводит к возникновению крайних случаев, когда невозможно надёжно очистить содержимое элемента `<noscript>`, поскольку Nokogiri не может полностью воспроизвести поведение синтаксического анализа браузера со включёнными сценариями.
#### :parser_options (Hash)
[Параметры синтаксического анализа](https://nokogiri.org/tutorials/parsing_an_html5_document.html?h=parsing+options#parsing-options), которые должны быть переданы в Nokogiri.```ruby
parser_options: {
max_errors: -1,
max_tree_depth: -1
}
URL-протоколы, которые разрешено использовать в определённых атрибутах. Если атрибут перечислен здесь и содержит протокол, отличный от указанных (или не содержит протокола вовсе), он будет удалён.```ruby protocols: { 'a' => {'href' => ['ftp', 'http', 'https', 'mailto']}, 'img' => {'src' => ['http', 'https']} }
Если вы хотите разрешить использование относительных URL-адресов, не имеющих протокола, включите символ `:relative` в массив протоколов:```ruby
protocols: {
'a' => {'href' => ['http', 'https', :relative]}
}
Если это true, Sanitize удалит содержимое всех элементов, не входящих в белый список, в дополнение к самим элементам. По умолчанию Sanitize оставляет безопасные части содержимого элемента после его удаления.
Если это Array или Set с именами элементов, то будет удалено содержимое только указанных элементов (при их фильтрации), а содержимое всех остальных отфильтрованных элементов будет сохранено.
Значение по умолчанию можно посмотреть в конфигурации по умолчанию.
Пользовательский HTML-трансформер или массив пользовательских трансформеров. Подробности см. в разделе Transformers ниже.
Hash с именами элементов, содержимое которых при удалении должно быть окружено пробелами для сохранения читаемости.
Каждое имя элемента является ключом, указывающим на другой Hash, который определяет конкретные пробелы, вставляемые :before и :after позиции удалённого элемента. Значение :after будет вставлено только в том случае, если у удалённого элемента есть дочерние элементы, и в этом случае оно будет вставлено после этих дочерних элементов.```ruby
whitespace_elements: {
'br' => { before: "\n", after: "" },
'div' => { before: "\n", after: "\n" },
'p' => { before: "\n", after: "\n" }
}
Элементы по умолчанию с добавленными пробелами до и после можно увидеть в [конфигурации по умолчанию](https://github.com/rgrove/sanitize/blob/HEAD/lib/sanitize/config/default.rb).
## Трансформеры
Трансформеры позволяют вам фильтровать и изменять HTML-узлы с помощью собственной пользовательской логики, поверх (или вместо) основного фильтра Sanitize. Трансформер — это любой объект, который отвечает на `call()` (например, lambda или proc).
Чтобы использовать один или несколько трансформеров, передайте их в параметр конфигурации `:transformers`. Вы можете передать один трансформер или массив трансформеров.```ruby
Sanitize.fragment(html, transformers: [
transformer_one,
transformer_two
])
Метод call() каждого преобразователя будет вызван один раз для каждого узла в HTML (включая элементы, текстовые узлы, комментарии и т.д.) и получит в качестве аргумента Hash, содержащий следующие элементы:
:config - Текущий Hash конфигурации Sanitize.
:is_allowlisted - true, если текущий узел был внесён в белый список предыдущим преобразователем, и false в противном случае. Как правило, дурной тон удалять узел, который предыдущий преобразователь внёс в белый список.
:node - Объект Nokogiri::XML::Node, представляющий HTML-узел. Узел может быть элементом, текстовым узлом, комментарием, CDATA-узлом или фрагментом документа. Используйте методы проверки Nokogiri (element?, text? и т.д.), чтобы выборочно игнорировать не интересующие вас типы узлов.
:node_allowlist - Набор объектов Nokogiri::XML::Node в текущем документе, которые были внесены в белый список предыдущими преобразователями, если таковые есть. Как правило, дурной тон удалять узел, который предыдущий преобразователь внёс в белый список.
:node_name - Имя текущего HTML-узла, всегда в нижнем регистре (например, "div" или "span"). Для узлов, не являющихся элементами, имя будет чем-то вроде "text", "comment", "#cdata-section", "#document-fragment" и т.д.
Преобразователь может ничего не возвращать, но при желании может вернуть Hash, который может содержать следующие элементы:
Nokogiri::XML::Node, которые нужно добавить в белый список документа в обход текущей конфигурации Sanitize. Эти конкретные узлы и все их атрибуты будут внесены в белый список, но их дочерние элементы — нет.Если преобразователь возвращает что-либо, кроме Hash, возвращаемое значение будет проигнорировано.
Каждый преобразователь имеет полный доступ к Nokogiri::XML::Node, переданному ему, и к остальной части документа через метод document() узла. Любые изменения, внесённые в текущий узел или в документ, мгновенно отразятся в документе и будут переданы последующим вызываемым преобразователям и самому Sanitize. Преобразователь может даже вызывать Sanitize внутренне для выполнения пользовательской очистки, если это необходимо.
Узлы передаются преобразователям в порядке их обхода. Sanitize выполняет обход сверху вниз, то есть узлы обходятся в том же порядке, в котором вы читали бы их в HTML, начиная с верхнего узла, затем его первого дочернего элемента и так далее.```ruby html = %[
foobar
]transformer = lambda do |env| puts env[:node_name] if env[:node].element? end
Sanitize.fragment(html, transformers: transformer)
Трансформеры обладают огромной мощью, включая способность полностью обходить встроенную фильтрацию Sanitize. Будьте осторожны! Ваша безопасность — в ваших руках.
### Пример: трансформер для разрешения URL изображений по домену
Следующий пример демонстрирует, как удалять элементы изображений, если только они не используют относительный URL или не размещены на определённом домене. Предполагается, что элемент `` и его атрибут `src` уже находятся в списке разрешённых.```ruby
require "uri"
image_allowlist_transformer = lambda do |env|
# Ignore everything except elements.
return unless env[:node_name] == "img"
node = env[:node]
image_uri = URI.parse(node["src"])
# Only allow relative URLs or URLs with the example.com domain. The
# image_uri.host.nil? check ensures that protocol-relative URLs like
# "//evil.com/foo.jpg" are not allowed.
unless image_uri.host == "example.com"
unless image_uri.host.nil? && image_uri.relative?
node.unlink # `Nokogiri::XML::Node#unlink` removes a node from the document
end
end
end
Следующий пример демонстрирует, как создать трансформер, который безопасно разрешит встраивание корректных YouTube-видео, без необходимости разрешать другие виды встраиваемого контента, что было бы неизбежно, если бы вы попытались сделать это, просто разрешив все элементы ` ].strip
Sanitize.fragment(html, transformers: youtube_transformer)