<p>最稳妥方案是使用 HtmlSanitizer 库而非正则或 HtmlEncode;它基于 AngleSharp 解析器,默认过滤 script、iframe 等危险标签及所有 on* 属性,并支持精细配置白名单与 CSS 安全策略。</p>
用 HtmlSanitizer 去掉 script、on\* 属性和危险标签最稳妥
直接上结论:别手写正则过滤 html,也别只靠 httputility.htmlencode,它只转义不删标签。真正防 xss,得用专门的净化库,htmlsanitizer 是目前 .net 生态里最成熟、配置灵活、且持续维护的选择。
常见错误是把用户输入先 HtmlEncode 再拼进 HTML —— 这在纯文本场景 OK,但一旦你把它放进 innerHTML 或服务端渲染的模板里(比如 @Html.Raw()),编码就被解开了,script 照样执行。
实操建议:
- 安装 NuGet 包:
HtmlSanitizer(作者是vitaly-t,不是过时的AngleSharp简单封装版) - 默认就禁用
<script>、<iframe>、<object>、<embed>和所有on\*事件属性(如onclick) - 允许保留的标签和属性要显式声明,比如只留
p、strong、a(且限制a的href只能是http://、https://或/开头) - 注意:它默认不处理 CSS 中的
expression()或url(javascript:...),需要开启AllowCssInStyleAttributes = false并配合SanitizeCss预处理器
var sanitizer = new HtmlSanitizer();
sanitizer.AllowedTags.Add("p");
sanitizer.AllowedTags.Add("a");
sanitizer.AllowedAttributes.Add("href");
sanitizer.AllowedSchemes.Add("https");
sanitizer.AllowDataAttributes = false;
sanitizer.RemovingTag += (s, e) => { /* 可选:记录被删了啥 */ };
string clean = sanitizer.Sanitize(dirtyHtml);为什么不能用 Regex.Replace 清除 <script>
因为 HTML 结构不规则,正则根本不可靠。一个典型翻车场景:<scr<script>ipt>、<SCRIPT SRC="x.js">、注释里的 <!-- <script> -->、或者 Unicode 变体(如 <script>>),正则要么漏杀,要么误杀正常内容。</script>>
更隐蔽的问题是:XSS 不只靠 <script>。攻击者会用 <img src=x onerror=alert(1)>、<a href="javascript:alert(1)">、甚至 <div style="background:url(javascript:alert(1))"> 绕过简单标签过滤。
立即学习“前端免费学习笔记(深入)”;
所以:
- 正则只适合做预处理(比如删掉明显恶意的
javascript:协议),不能当主防线 -
HtmlSanitizer内部用的是 HTML 解析器(基于AngleSharp),能真正理解 DOM 结构,按语义过滤 - 如果你硬要用正则,至少加个
RegexOptions.IgnoreCase | RegexOptions.Compiled,但依然不推荐
HtmlEncode 和 Sanitize 的使用边界在哪
一句话:输出到 HTML 文本节点用 HtmlEncode,输出到 HTML 结构上下文(比如 innerHTML、Razor 的 @Html.Raw()、Vue/React 的 v-html)必须用 Sanitize。
典型混淆场景:
- 用户昵称显示在
<div></div>里 → 用HttpUtility.HtmlEncode(nick)足够 - 用户发的富文本评论(含
<b>、<a>)要渲染成真实 HTML → 必须走HtmlSanitizer.Sanitize(html) - 把用户输入拼进 JS 字符串(如
var msg = "@Model.Input";)→ 这时候该用JavaScriptStringEncode,不是 HTML 编码 - API 返回 JSON 时,字符串字段值仍需
HtmlEncode(如果前端会直接innerHTML渲染),否则 JSON-injection + XSS 双重风险
ASP.NET Core 中集成 HtmlSanitizer 的坑
容易忽略的一点:HtmlSanitizer 默认不处理自闭合标签里的属性(比如 <img onload="x">),但它会删掉整个 <img> —— 如果你允许 img 标签,就得手动配置白名单并禁用危险属性:
- 加
sanitizer.AllowedTags.Add("img");后,必须同步加sanitizer.AllowedAttributes.Remove("onload");等事件属性 - 它不自动过滤
src里的javascript:,得自己加校验逻辑或用sanitizer.RemovingAttribute事件拦截 - 在中间件里全局使用时,别把
HtmlSanitizer实例设为 static 单例然后反复调用Sanitize()—— 它不是线程安全的,每次应 new 一个,或用IServiceProvider注册为 Scoped - 性能上,一次 Sanitize 约耗时 0.5–3ms(视 HTML 长度),比正则慢,但安全代价值得
实际项目里最常被绕过的点,是「富文本编辑器上传的 HTML」和「Markdown 渲染后生成的 HTML」——这两处往往跳过了 Sanitize 步骤,直接进了页面。只要没走解析器级净化,再小心的前端校验也拦不住构造好的 payload。



















