
Sanitize 是一个基于允许列表的 HTML 和 CSS 清理器。它从字符串中移除所有 HTML 和/或 CSS,但你选择允许的元素、属性和属性值除外。
通过简单的配置语法,你可以告诉 Sanitize 允许某些 HTML 元素、这些元素中的某些属性,甚至包含 URL 的属性中的某些 URL 协议。你还可以允许包含 CSS 的元素或属性中的特定 CSS 属性、@规则和 URL 协议。任何未明确允许的 HTML 或 CSS 都将被移除。
Sanitize 基于 Nokogiri HTML5 解析器(其解析 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> 元素内的 CSS 样式表style 属性内的 CSS 属性[!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 in HTML
要对 HTML 片段或文档中的 CSS 进行净化,首先将 `<style>` 元素和/或 `style` 属性加入白名单。然后将你希望允许的 CSS 属性、@ 规则和 URL 协议加入白名单。你还可以选择是否允许 CSS 注释或浏览器兼容性 hack。```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。要在 HTML 上下文中安全地清理 CSS(例如在
<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 ))
### 配置设置
#### :add_attributes (Hash)
要添加到特定元素的属性。如果该属性已存在,将替换为在此处指定的值。请将所有元素名称和属性均指定为小写。```ruby
add_attributes: {
'a' => {'rel' => 'nofollow'}
}
Whether or not to allow HTML comments. Allowing comments is strongly discouraged, since IE allows script execution within conditional comments. The default value is false.
是否允许 HTML 注释。强烈不建议允许注释,因为 IE 允许在条件注释中执行脚本。默认值为 false。
Whether or not to allow well-formed HTML doctype declarations such as "" when sanitizing a document. This setting is ignored when sanitizing fragments. The default value is false.
是否允许在净化文档时使用格式良好的 HTML doctype 声明,例如 ""。净化片段时此设置将被忽略。默认值为 false。
Attributes to allow on specific elements. Specify all element names and attributes in lowercase.
允许在特定元素上使用的属性。所有元素名称和属性均需使用小写指定。```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 配置设置的哈希(无论是独立 CSS 还是嵌入在 HTML 中)。
##### :css => :allow_comments (boolean)
是否允许 CSS 注释。默认值为 `false`。
##### :css => :allow_hacks (boolean)
是否允许浏览器兼容性 hacks,例如 IE 的 `*` 和 `_` hacks。这些通常无害,但从技术上讲会导致 CSS 无效。默认值为 `false`。
##### :css => :at_rules (Array 或 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 或 Set)
允许的 CSS [at-rules][at-rules] 名称,这些规则可能具有包含 CSS 属性的关联块。诸如 `font-face` 和 `page` 之类的 at-rules 属于此类别。名称应使用小写指定。
##### :css => :at_rules_with_styles (Array 或 Set)
允许的 CSS [at-rules][at-rules] 名称,这些规则可能具有包含样式规则的关联块。诸如 `media` 和 `keyframes` 之类的 at-rules 属于此类别。名称应使用小写指定。
##### :css => :import_url_validator
这是一个 `Proc`(或其他可调用对象),将被调用并传入为任何 `@import` [at-rules][at-rules] 指定的 URL。
您可以使用它来限制可导入的内容,例如类似以下内容可将 `@import` 限制为 Google Fonts URL:```ruby
Proc.new { |url| url.start_with?("https://fonts.googleapis.com") }
允许的 CSS 属性名称列表。名称应使用小写指定。
允许在 CSS URL 中使用的 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)
提供给 Nokogiri 的[解析选项](https://nokogiri.org/tutorials/parsing_an_html5_document.html?h=parsing+options#parsing-options)。```ruby
parser_options: {
max_errors: -1,
max_tree_depth: -1
}
特定属性中允许的 URL 协议。如果某个属性在此列出,且其包含的协议不在指定范围内(或完全不包含协议),则该属性将被移除。```ruby protocols: { 'a' => {'href' => ['ftp', 'http', 'https', 'mailto']}, 'img' => {'src' => ['http', 'https']} }
如果你希望允许使用没有协议(protocol)的相对 URL,请在协议数组中包含符号 `:relative`:```ruby
protocols: {
'a' => {'href' => ['http', 'https', :relative]}
}
如果为 true,Sanitize 将在移除元素本身之外,还移除任何未列入白名单的元素的内容。默认情况下,当元素被移除时,Sanitize 会保留元素内容中的安全部分。
如果这是一个元素名称的数组或集合,则仅移除指定元素(在被过滤时)的内容,而所有其他被过滤元素的内容将被保留。
默认值可在默认配置中查看。
自定义 HTML transformer 或自定义 transformer 数组。有关详细信息,请参阅下文中的 Transformers 部分。
元素名称的哈希表,当这些元素被移除时,其内容周围将插入空白字符以保持可读性。
每个元素名称是一个键,指向另一个哈希表,该哈希表提供应在被移除元素位置 :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)中查看。
## 转换器
转换器允许你使用自己的自定义逻辑,在 Sanitize 的核心过滤器之上(或代替它)来过滤和修改 HTML 节点。转换器是任何响应 `call()` 方法的对象(例如 lambda 或 proc)。
要使用一个或多个转换器,请将它们传递给 `:transformers` 配置设置。你可以传递单个转换器或一个转换器数组。```ruby
Sanitize.fragment(html, transformers: [
transformer_one,
transformer_two
])
每个转换器的 call() 方法会对 HTML 中的每个节点(包括元素、文本节点、注释等)各调用一次,并接收一个包含以下项的 Hash 作为参数:
:config - 当前 Sanitize 配置 Hash。
:is_allowlisted - 如果当前节点已被先前的转换器列入允许列表,则为 true,否则为 false。移除已被先前转换器列入允许列表的节点通常是不好的做法。
:node - 表示 HTML 节点的 Nokogiri::XML::Node 对象。该节点可以是元素、文本节点、注释、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)
Transformers 拥有巨大的能力,包括完全绕过 Sanitize 内置过滤的能力。请小心!你的安全掌握在自己手中。
### 示例:按域名允许图片 URL 的 Transformer
以下示例演示了如何移除图片元素,除非它们使用相对 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)