富文本编辑器是Web应用中最常见的XSS攻击入口之一。用户提交的HTML内容若未经过严格处理,攻击者可以注入<script>标签、事件属性或javascript:链接,在管理员或其他用户浏览时执行任意脚本,造成存储型XSS。本文聚焦于两类核心防护策略——白名单过滤与HTML消毒,并给出可落地的实施方案。
先明确边界:富文本XSS的防护层次
在动手写过滤代码之前,必须清楚:没有一种万能方案能应对所有场景。防护需要分层:输入过滤(白名单)、输出编码、内容安全策略(CSP)以及服务端消毒。本文重点讨论白名单与消毒,但会提及与其他层次的配合。
同时,要区分“过滤”和“消毒”的差异:过滤是移除或拒绝不合规内容;消毒是清理HTML中危险部分但保留安全结构。实践中两者常结合使用。
第一步:配置白名单,只允许安全标签和属性
白名单策略的核心是:只允许明确列出的HTML标签和属性,其余一律剥离或拒绝。这远比黑名单(拦截已知危险标签)可靠,因为黑名单无法覆盖所有变体。
确定白名单范围
根据业务需求,常见的安全白名单标签包括:<p>、<br>、<strong>、<em>、<ul>、<ol>、<li>、<a>、<img>、<blockquote>、<code>等。属性方面,<a>只允许href、title,且href必须校验协议(仅允许http、https、mailto);<img>只允许src、alt,且src同样校验协议。
实现方式
可以自己编写解析器,但强烈建议使用成熟库,如Cloudflare Workers生态中的cheerio配合sanitize-html,或Python的bleach。以下是一个Node.js示例(使用sanitize-html):
const sanitizeHtml = require('sanitize-html');
const clean = sanitizeHtml(dirtyHtml, {
allowedTags: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'img', 'blockquote', 'code'],
allowedAttributes: {
'a': ['href', 'title'],
'img': ['src', 'alt']
},
allowedSchemes: ['http', 'https', 'mailto']
});
注意:sanitize-html默认会剥离所有不在白名单中的标签和属性,这符合白名单策略。
第二步:HTML消毒——处理嵌套与畸形内容
白名单过滤只是第一步。攻击者可能利用HTML解析器的容错性,构造畸形标签或嵌套结构绕过过滤。因此,消毒必须基于真正的HTML解析器,而不是正则表达式。解析器会将内容解析为DOM树,然后只保留白名单节点,并规范化属性值。
以jsoup(Java)为例:
String clean = Jsoup.clean(dirtyHtml, Safelist.basic());
// 或自定义Safelist
jsoup的Safelist.basic()内置了常见安全标签,并自动处理协议和属性。如果你使用PHP,可考虑HTMLPurifier;Ruby则可用Sanitize。
第三步:输出编码与CSP——纵深防御
即使做了消毒,也不要完全依赖它。在输出到HTML上下文时,仍需对文本节点进行编码,防止任何遗漏。例如,将<转义为<。对于富文本内容,由于是HTML输出,不能整体编码,但可以对属性值编码。
同时,启用Cloudflare等CDN时,可结合CSP响应头。CSP的script-src指令可以限制脚本执行来源,即使有XSS注入,浏览器也会阻止执行。这是重要的兜底措施。更多CSP配置可参考Web安全响应头配置指南。
常见误区与失败条件
- 误区:只做前端过滤。前端过滤只是用户体验,攻击者可直接构造请求绕过,必须服务端处理。
- 误区:使用黑名单。黑名单无法穷举所有危险标签和属性,如
<svg>、<math>等。 - 误区:正则表达式清洗。正则无法正确解析HTML,且容易遗漏编码变体(如
<script>)。 - 失败条件:如果白名单包含
<style>或<iframe>,需特别小心,这些标签可能引发CSS注入或点击劫持。建议默认不允许。
实操:整合到应用流程
以Node.js为例,将消毒函数集成到API层:
app.post('/api/content', (req, res) => {
const cleanContent = sanitizeHtml(req.body.content, options);
// 存储cleanContent
});
同时,在输出时,如果是在模板中渲染,确保使用模板引擎的自动转义机制(如EJS的<%= %>),但富文本内容需要标记为安全HTML(如EJS的<%- %>),此时必须确保内容已经过消毒。
总结与扩展阅读
富文本编辑器的XSS防护,核心是服务端白名单过滤与HTML消毒,并配合CSP和输出编码形成纵深防御。没有绝对安全,但通过上述策略可大幅降低风险。
进一步了解XSS攻击原理,可参考XSS攻击原理与防护方法详解;输入过滤与输出编码的更多细节,见如何通过输入过滤和输出编码防止XSS攻击。
参考资料
延伸阅读
