基于规则的 HTML 清理器






4.93/5 (16投票s)
一个基于白名单的保守型HTML清理器,使用规则。适用于限制文档标记,清理带有限制性标记的评论,并防止XSS攻击。
引言
虽然市面上有许多HTML清理器,但我需要一个服务器端的HTML清理器,它要保守,使用白名单,并且仍然允许相对完整的HTML标记。我当时正在使用wysihtml5编辑器来启用文档编辑,并对其出色的客户端HTML清理器印象深刻,但我需要在服务器端也有一个类似但也可配置的东西。
这个HTML清理器,恰如其分地命名为HtmlRuleSanitizer
,旨在提供 exactly 这个。它构建在HTML Agility Pack (HAP)之上,用于执行HTML DOM解析和操作。
使用HtmlRuleSanitizer
清理HTML非常简单。使用预定义的清理器进行简单的HTML5代码使用,只需要以下两行代码
var sanitizer = HtmlSanitizer.SimpleHtml5Sanitizer();
string cleanHtml = sanitizer.Sanitize(dirtyHtml);
它将把这段明显脏的HTML...
<h1>Heading</h1> <p onclick="alert('gotcha!')">Some comments<span></span></p> <script type="text/javascript">Illegal script()</script> <p><a href="http://www.google.com/">Nofollow legal link</a> and here's another one: <a href="javascript:alert('test')">Obviously I'm illegal</a></p>
...转换为这段干净安全的HTML
<h1>Heading</h1>
<p>Some comments</p>
<p><a href="http://www.google.com/" target="_blank" rel="nofollow">Nofollow legal link</a>
and here's another one:
Obviously I'm illegal</p>
在本文的其余部分,我将解释我开发这个HTML清理器的方法。文章最后将提供更详细的使用说明和要点总结。
背景
在开发一个合同管理应用程序时,我希望用户能够编辑基本的HTML文档,其结构是HTML5标准的一个已知子集。wysihtml5编辑器提供了出色的客户端实现,但我还需要强大的服务器端强制执行。
由于需要允许特定HTML5子集通过,同时又要剥离所有不安全的内容,因此大多数现有的清理解决方案都不够。例如,Microsoft的Web Protection Library(以前称为AntiXSS)似乎几乎剥离了所有HTML标签,这不适合我的用途。此外,它也不可配置,并且已视为生命周期结束。
wysihtml5编辑器有一个白名单和基于规则的客户端HTML清理器,我非常喜欢。由于在服务器端实现它似乎并不太困难,所以我决定尝试一下。HtmlRuleSanitizer
就是结果。
标签白名单
第一个要求是能够绝对去除所有HTML标签,除了那些符合我的文档结构的标签。通过这样做,我希望确保将来能够轻松地将文档转换为其他格式,如RTF、Word和PDF,而不会突然遇到各种难以处理的元素,因为这些元素没有直接的等价物。这个功能在评论系统等场景中也非常可取,因为您只想允许使用一些HTML标签,如<a>
、<strong>
和<em>
。
通过创建一个规则来白名单一个标签
sanitizer.Tag("p");
标签展平
当用户使用某种我不喜欢的标签时,完全删除该标签及其内容可能有些过度。为此,内置了标签展平功能:标签本身被删除,但其内容得以保留。这样,例如可以去除不必要的<div>
元素包装的内容,同时保留该内容本身。
sanitizer.Tag("div").NoAttributes(SanitizerOperation.FlattenTag);
清理
虽然清理通常指的是检查和清理HTML的完整过程,但我还想避免一个常见问题,即浏览器编辑器会留下空的标签。在某些编辑器中,双击粗体按钮会导致保留一个<b></b>
标签。我不需要这个,我们把它去掉。按照以下方式移除白名单中的空标签
sanitizer.Tag("strong").RemoveEmpty();
清理分两步进行:第一步是“下游”步骤,在此过程中,清理器会深入文档树,移除非白名单节点和空节点。第二步是“上游”步骤,在此过程中,当清理器遍历文档树返回时,每个节点都会再次被检查是否为空。这是必需的,因为下游步骤可能会导致上游节点由于其子标签被移除而变为空。
CSS白名单
由于<center>
标签在HTML5中已被弃用,而且我也想能够将文本右对齐,所以我需要一些CSS类能够通过。同样,其他所有内容都应该被排除,所以我们使用白名单。
以下是如何白名单一个CSS类
sanitizer.AllowCss("legal-css");
标签重命名
如何处理那些仍然能够提交包含例如<b>
标签的文档的人?<b>
标签的使用是不推荐的,而且我不想在稍后转换文档时同时处理<strong>
和<b>
标签。为此,HtmlSanitizer
配备了一个标签重命名注册表。
使用以下方式完成标签重命名的规范
sanitizer.Tag("b").Rename("strong");
属性强制和检查
我的软件的用户可以随意添加链接,但不能耍花招!每个链接都需要是nofollow的,并且需要在新窗口中打开。
我只需要一种类型的属性进行检查:链接的href
属性。只允许具有有效URL和允许的URI方案(没有javascript:blbla
之类的伎俩)的链接。为了扩展性,我添加了一个属性检查注册表,可以在其中注册属性检查回调。
属性检查以及nofollow和新窗口目标窗口的强制执行可以这样完成
sanitizer.Tag("a").SetAttribute("target", "_blank") .SetAttribute("rel", "nofollow") .CheckAttribute("href", HtmlSanitizerCheckType.Url) .RemoveEmpty() .NoAttributes(SanitizerOperation.FlattenTag);
请注意,上面代码中的最后一行包含另一个好东西。一个没有剩余属性的<a>
标签显然是垃圾,所以我们可以指示清理器将其展平。
属性白名单
另一个潜在的危险是未能剥离onclick
等属性。因此,任何没有配置检查或覆盖的属性都会被移除。class属性是唯一的例外。可以使用AllowAttributes
方法白名单其他属性
sanitizer.Tag("span").AllowAttributes("style");
HTML实体编码
防止XSS攻击的最后一步是强制在应编码的地方编码HTML实体。由于清理器完全解析输入HTML,原则上不应该出现任何HTML实体技巧的问题。如果(故意)未能编码所有HTML实体导致解析器错误地解析HTML,这只会导致标签被完全忽略或由于白名单方法而被删除。此外,清理器不评估任何脚本,因此对清理器本身进行故意攻击的漏洞应该非常有限。
然而,这并不意味着任何其他将使用清理后的HTML的解析器或程序不会受到此类攻击的攻击。此外,带有未编码实体的HTML根本不是有效的HTML。另一方面,HTML实体编码并不像简单地将所有HTML通过单一编码方法那样简单。
清理器依赖于标准的.NET框架WebUtility
类进行HTML实体编码。默认情况下,HTML实体编码会强制应用于HTML文档的所有文本部分。为了防止对正确编码的实体进行双重编码,首先会解码所有实体。然后对文本节点实体进行编码,并替换文本节点。生成的代码片段如下
if (node.NodeType == HtmlNodeType.Text && EncodeHtmlEntities)
{
var deentitized = WebUtility.HtmlDecode(node.InnerText);
var entitized = WebUtility.HtmlEncode(deentitized);
var replacement = HtmlTextNode.CreateNode(entitized);
node.ParentNode.ReplaceChild(replacement, node);
return;
}
配置
我希望HtmlSanitizer
易于配置。我使用扩展方法编写了一个小的流畅风格的配置接口。该接口定义在HtmlSanitizerFluentHelper
类中。该接口在上述示例中被广泛使用。
使用HtmlRuleSanitizer
下载清理器后,要能够使用该清理器的第一件事是下载Html Agility Pack (HAP)。要么在他们的codeplex网站获取,要么获取他们的NuGet包。如果您使用HtmlRuleSanitizer NuGet包,Html Agility Pack将为您安装。
HtmlRuleSanitizer
附带两个预设配置。使用预定义的清理器进行简单的HTML5代码使用,只需要以下两行代码
var sanitizer = HtmlSanitizer.SimpleHtml5Sanitizer();
string cleanHtml = sanitizer.Sanitize(dirtyHtml);
当您想要清理包含<html>
和<body>
的文档时,请使用SimpleHtml5DocumentSanitizer
var sanitizer = HtmlSanitizer.SimpleHtml5DocumentSanitizer(); string cleanHtmlDoc = sanitizer.Sanitize(dirtyHtmlDoc);
配置
简单的HTML5清理规则集定义如下。这可以作为配置更完整规则集的良好示例。
var sanitizer = new HtmlSanitizer(); sanitizer.WhiteListMode = true; sanitizer.Tag("h1").RemoveEmpty(); sanitizer.Tag("h2").RemoveEmpty(); sanitizer.Tag("h3").RemoveEmpty(); sanitizer.Tag("h4").RemoveEmpty(); sanitizer.Tag("h5").RemoveEmpty(); sanitizer.Tag("strong").RemoveEmpty(); sanitizer.Tag("b").Rename("strong").RemoveEmpty(); sanitizer.Tag("i").RemoveEmpty(); sanitizer.Tag("em"); sanitizer.Tag("br"); sanitizer.Tag("p"); sanitizer.Tag("div").NoAttributes(SanitizerOperation.FlattenTag); sanitizer.Tag("span").RemoveEmpty(); sanitizer.Tag("ul"); sanitizer.Tag("ol"); sanitizer.Tag("li"); sanitizer.Tag("a").SetAttribute("target", "_blank") .SetAttribute("rel", "nofollow") .CheckAttribute("href", HtmlSanitizerCheckType.Url) .RemoveEmpty() .NoAttributes(SanitizerOperation.FlattenTag);
您可以自由定义任何新配置或使用流畅的配置接口扩展现有配置。
关注点
没有测试的HTML清理器有什么用?虽然我不能完全保证这个清理器能够保护您免受任何跨站点脚本攻击和其他伎俩,但我确实添加了单元测试来支持我的声明,即它正在工作。此外,任何能够发现弱点的人,我都会很乐意听取他们的建议。一些测试来自OWASP,后者被证明是XSS攻击的宝贵信息来源。
在为我最初的问题寻找解决方案时,我偶然发现了mganss的这个清理器,它似乎是这里介绍的HTML清理器的一个非常好的替代方案。它具有许多相同的特性,但使用了一个名为CsQuery的库进行HTML DOM解析。
历史
查找最新版本,请参见https://github.com/Vereyon/HtmlRuleSanitizer
2016年8月13日:版本1.2.0:实现了HTML实体编码和级联空节点移除。
2015年6月17日:版本1.1.0:添加了标签属性白名单和附加单元测试。