正则过滤无法防御xss,必须用html解析器白名单清洗;htmlsanitizer是c#最可靠的方案,自动剥离事件属性和危险协议,而htmlagilitypack仅适合解析非安全场景。

别用正则删 <.></.> 做 XSS 防御——它根本拦不住 <img src="x" onerror="alert(1)"> 这类 payload。真正防 XSS 的清洗,必须基于 HTML 解析器做白名单控制,而不是字符串替换。
HtmlSanitizer 是目前 C# 里最靠谱的 XSS 防御库
它不是“去标签”,而是按规则保留安全子集:默认只放行 <p></p>、<strong></strong>、<a></a>(且自动过滤 href 中的 javascript:)、<img>(且校验 src 协议),所有事件属性(onload、onclick 等)和危险协议(data:、vbscript:)一律剥离。
安装:Install-Package HtmlSanitizer
基础用法:
var sanitizer = new HtmlSanitizer();
sanitizer.AllowedTags.Add("article"); // 手动加允许标签
sanitizer.AllowedAttributes.Add("cite"); // 允许 cite 属性
string clean = sanitizer.Sanitize(dirtyHtml);
关键点:
- 默认不放行
<script></script>、<style></style>、<iframe></iframe>、<object></object>—— 不用额外写规则 - 自动解码并重编码属性值,防止双写编码绕过(如
javascript:) - 对
<a href="javascript:..."></a>会清空href值,不是简单删标签
为什么 HtmlAgilityPack 不适合防 XSS
HtmlAgilityPack 是解析/清洗工具,不是安全 sanitizer。它能帮你取 InnerText 或删标签,但无法识别语义风险:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 它不会拦截
<div onclick="fetch('/api/delete')"> —— 这仍是合法 HTML<li>它不会校验 <code><img src="data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9YWxlcnQoMSk+>%20%E8%BF%99%E7%A7%8D%E5%86%85%E8%81%94%20SVG%20XSS - %E5%AE%83%E9%BB%98%E8%AE%A4%E4%B8%8D%E5%A4%84%E7%90%86%20CSS%20%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%88
expression(...)%EF%BC%89%E6%88%96%20IE%20%E6%9D%A1%E4%BB%B6%E6%B3%A8%E9%87%8A
%E5%A6%82%E6%9E%9C%E4%BD%A0%E5%8F%AA%E6%98%AF%E2%80%9C%E6%8F%90%E5%8F%96%E7%BA%AF%E6%96%87%E6%9C%AC%E5%B1%95%E7%A4%BA%E2%80%9D%EF%BC%8C%E7%94%A8%20HtmlAgilityPack%20%E6%B2%A1%E9%97%AE%E9%A2%98%EF%BC%9B%E4%BD%86%E5%8F%AA%E8%A6%81%E8%BE%93%E5%87%BA%E5%88%B0%E9%A1%B5%E9%9D%A2%EF%BC%88innerHTML%E3%80%81@Html.Raw()%EF%BC%89%EF%BC%8C%E5%B0%B1%E5%BF%85%E9%A1%BB%E6%8D%A2%20HtmlSanitizer%E3%80%82
Regex.Replace%20%E6%B8%85%E6%B4%97%E5%9C%A8%E4%BB%80%E4%B9%88%E5%9C%BA%E6%99%AF%E4%B8%8B%E8%BF%98%E8%83%BD%E7%94%A8
%E4%BB%85%E9%99%90%E5%8F%AF%E4%BF%A1%E6%9D%A5%E6%BA%90%E3%80%81%E7%BB%93%E6%9E%84%E6%9E%81%E7%AE%80%E3%80%81%E4%B8%94%E4%B8%8D%E5%8F%82%E4%B8%8E%20DOM%20%E6%B8%B2%E6%9F%93%E7%9A%84%E5%9C%BA%E5%90%88%EF%BC%8C%E6%AF%94%E5%A6%82%E6%97%A5%E5%BF%97%E6%91%98%E8%A6%81%E3%80%81%E5%90%8E%E5%8F%B0%E5%AD%97%E6%AE%B5%E9%A2%84%E8%A7%88%EF%BC%9A
- %E5%B7%B2%E7%9F%A5%E5%8F%AA%E6%9C%89%20
<b>%E3%80%81<i>%E3%80%81<br>%EF%BC%8C%E6%97%A0%E5%B1%9E%E6%80%A7%E3%80%81%E6%97%A0%E5%B5%8C%E5%A5%97 - %E5%BF%85%E9%A1%BB%E6%8F%90%E5%89%8D%E7%A7%BB%E9%99%A4%E6%B3%A8%E9%87%8A%EF%BC%9A
Regex.Replace(html,%20@">", "") - 必须禁用贪婪匹配:
Regex.Replace(html, @"]*>", "", RegexOptions.Singleline) - 不能处理
<svg onload="..."></svg>或<math href="xss"></math>这类现代 HTML5 标签 - 双重 HTML 实体:
<script>alert(1)</script>→ 必须先WebUtility.HtmlDecode两次,再进HtmlSanitizer - UTF-8 BOM(
\uFEFF)会导致HtmlSanitizer解析失败,报错Unexpected token→ 读取时用new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true) - Word 粘贴内容含大量
mso-*样式和冗余<font></font>标签 →HtmlSanitizer默认不清理这些,需配合AllowedCssProperties.Clear()和自定义CssClassWhitelist
一旦输入来自用户编辑器、富文本粘贴、或第三方 API,regex 就该退役了。
容易被忽略的细节:编码 + BOM + 多层嵌套实体
真实数据常带干扰:
清洗不是一步到位的动作,而是按数据来源分层:先解码,再 BOM 处理,再 sanitizer,最后才是业务逻辑裁剪。










