Sanitize 是一个基于允许列表的 HTML 和 CSS 清理器。它会从字符串中移除所有 HTML 和/或 CSS,只保留你选择允许的元素、属性和属性值。
使用简单的配置语法,你可以让 Sanitize 允许特定的 HTML 元素、这些元素中的特定属性,甚至包含 URL 的属性中的特定 URL 协议。你还可以允许特定的 CSS 属性、@ 规则,以及包含 CSS 的元素或属性中的 URL 协议。任何你未明确允许的 HTML 或 CSS 都会被移除。
Sanitize 基于 Nokogiri HTML5 解析器(其解析 HTML 的方式与现代浏览器相同)和 Crass(其解析 CSS 的方式与现代浏览器相同)。只要你的允许列表配置仅允许安全的标记和 CSS,即使是最畸形或恶意的输入也会被转换为安全的输出。
[!WARNING]
Sanitize 无法完全清理
<math>或<svg>元素的内容。MathML 和 SVG 元素是外来元素,不遵循常规的 HTML 解析规则。默认情况下,Sanitize 会移除所有 MathML 和 SVG 元素。如果你将 MathML 或 SVG 元素添加到自定义元素允许列表中,可能会在你的应用程序中引入安全漏洞。
gem install sanitize
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,只保留安全的文本。
html = '<b><a href="http://foo.com/">foo</a></b><img src="https://raw.githubusercontent.com/rgrove/sanitize/main/bar.jpg">'
Sanitize.fragment(html)
# => "foo"
要将某些元素保留下来,请将它们添加到元素允许列表中。
Sanitize.fragment(html, elements: ['b'])
# => "<b>foo</b>"
在清理文档时,必须将 <html> 元素加入允许列表。你还可以将 :allow_doctype 设置为 true,以允许格式良好的文档类型定义。
html = %[
<!DOCTYPE html>
<html>
<b><a href="http://foo.com/">foo</a></b><img src="https://raw.githubusercontent.com/rgrove/sanitize/main/bar.jpg">
</html>
]
Sanitize.document(html,
allow_doctype: true,
elements: ['html']
)
# => "<!DOCTYPE html><html>foo\n \n</html>"
要清理 HTML 片段或文档中的 CSS,首先将 <style> 元素和/或 style 属性加入允许列表。然后将你希望允许的 CSS 属性、@ 规则和 URL 协议加入允许列表。你还可以选择是否允许 CSS 注释或浏览器兼容性 hack。
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 注入攻击。
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)
# => %[
#
#
# a { text-decoration: none; }
#
# a:hover {
#
# text-decoration: underline;
# }
# ]
Sanitize::CSS.properties(%[
left: expression(alert('xss!'));
text-decoration: underline;
], Sanitize::Config::RELAXED)
# => %[
#
# text-decoration: underline;
# ]
除了超安全的默认设置外,Sanitize 还附带三种其他内置配置,你可以直接使用,也可以根据需要进行调整。
仅允许非常简单的内联标记。不允许链接、图像或块级元素。
Sanitize.fragment(html, Sanitize::Config::RESTRICTED)
# => "<b>foo</b>"
允许使用多种标记,包括格式化元素、链接和列表。
不允许使用图片和表格,链接仅限于 FTP、HTTP、HTTPS 和 mailto 协议,并且会为所有链接添加 rel="nofollow" 属性,以减轻 SEO 垃圾信息。
Sanitize.fragment(html, Sanitize::Config::BASIC)
# => '<b><a href="http://foo.com/" rel="nofollow">foo</a></b>'
允许更广泛的标记,包括图像和表格,以及安全的 CSS。链接仍限于 FTP、HTTP、HTTPS 和 mailto 协议,而图像则限于 HTTP 和 HTTPS。在此模式下,不会向链接添加 rel="nofollow"。
Sanitize.fragment(html, Sanitize::Config::RELAXED)
# => '<b><a href="http://foo.com/">foo</a></b><img src="https://raw.githubusercontent.com/rgrove/sanitize/main/bar.jpg">'
如果内置模式无法满足你的需求,你可以轻松指定自定义配置:
Sanitize.fragment(html,
elements: ['a', 'span'],
attributes: {
'a' => ['href', 'title'],
'span' => ['class']
},
protocols: {
'a' => {'href' => ['http', 'https', 'mailto']}
}
)
你也可以从 Sanitize 的内置配置之一开始,然后根据需要进行自定义。
内置配置被深度冻结,以防止人们(无论是意外还是恶意)修改它们。要自定义内置配置,请使用 Sanitize::Config.merge() 创建一个新副本,如下所示:
# 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 中现有元素列表的副本中。如果你想要用自己的元素完全覆盖 elements 数组,可以省略 + 操作:
# Overwrite :elements instead of creating a copy with new entries.
Sanitize.fragment(html, Sanitize::Config.merge(Sanitize::Config::BASIC,
elements: ['div', 'table'],
remove_contents: true
))
要添加到特定元素的属性。如果该属性已存在,则将其替换为此处指定的值。请以小写形式指定所有元素名称和属性。
add_attributes: {
'a' => {'rel' => 'nofollow'}
}
是否允许 HTML 注释。强烈不建议允许注释,因为 IE 允许在条件注释中执行脚本。默认值为 false。
在清理文档时是否允许格式良好的 HTML doctype 声明(例如 "")。在清理片段时此设置会被忽略。默认值为 false。
允许在特定元素上使用的属性。请以小写形式指定所有元素名称和属性。
attributes: {
'a' => ['href', 'title'],
'blockquote' => ['cite'],
'img' => ['alt', 'src', 'title']
}
如果希望允许所有元素上的某些属性,请使用符号 :all 代替元素名称。
# Allow the class attribute on all elements.
attributes: {
:all => ['class'],
'a' => ['href', 'title']
}
要允许任意 HTML5 data-* 属性,请使用符号 :data 代替属性名。
# Allow arbitrary HTML5 data-* attributes on <div> elements.
attributes: {
'div' => [:data]
}
用于在清理 CSS(独立或嵌入 HTML 中)时使用的 CSS 配置设置的哈希。
是否允许 CSS 注释。默认值为 false。
是否允许浏览器兼容性 hack,例如 IE 的 * 和 _ hack。这些通常无害,但技术上会导致无效的 CSS。默认值为 false。
允许的 CSS at-rules 名称,这些规则可能没有关联的块,例如 import 或 charset。名称应以小写形式指定。
允许的 CSS at-rules 名称,这些规则可能有关联的块,其中包含 CSS 属性。像 font-face 和 page 这样的 at-rules 属于此类。名称应以小写形式指定。
允许的 CSS at-rules 名称,这些规则可能有关联的块,其中包含样式规则。像 media 和 keyframes 这样的 at-rules 属于此类。名称应以小写形式指定。
这是一个 Proc(或其他可调用对象),它会被调用,并传入任何 @import at-rules 指定的 URL。
你可以使用它来限制可以导入的内容,例如类似下面的代码,将 @import 限制为 Google Fonts 的 URL:
Proc.new { |url| url.start_with?("https://fonts.googleapis.com") }