富文本编辑器存储内容后渲染时如何有效拦截XSS攻击?

♫珊珊 阅读 51

我在用Quill编辑器实现富文本功能时遇到问题,用户输入的<script>标签在存储到数据库后仍然会被渲染执行。之前用sanitize-html做了过滤,但发现标签被正常保留,而恶意脚本却没被拦截,这是哪里配置错了?

尝试过这样设置白名单:


const options = {
  allowedTags: [ 'p', 'img', 'br', 'div' ],
  allowedAttributes: {
    'img': ['src']
  }
};
sanitizeHtml(dirty, options);

但测试时输入<div><script>alert(1)</script></div>,渲染后控制台居然弹窗了,这说明过滤逻辑存在漏洞,该怎么调整配置才能彻底阻断脚本执行?

我来解答 赞 7 收藏
二维码
手机扫码查看
2 条解答
FSD-莉娜
你这个配置根本没拦住 script 标签的执行,问题出在你只过滤了标签和属性,但没处理 内联事件 和 危险的 href / src 协议,更没开 disallowedTagsMode,导致 script 虽然被“过滤”了,但它的内容可能被保留成纯文本,或者浏览器在某些场景下还能解析。

先说结论:前端 sanitize 是第一道防线,但别指望它 100% 靠谱,后端必须再做一层清洗,尤其是入库前。

先看你的问题——你写的 allowedTags 里没包含 script,按理说应该被删掉,但为什么还能执行?
因为 sanitize-html 默认 disallowedTagsMode: 'escape',会把 script 里的内容转成 HTML 实体,比如 <script>alert(1)</script>,这样就安全了。
但如果你的 dirty 是已经被浏览器解析过一遍的 HTML 字符串(比如你直接用 innerHTML 插进去过),那 script 标签可能早就执行过了——sanitize 必须在 HTML 还是纯字符串时就做!

你现在的风险点有几个:

1. 没配置 disallowedTagsMode,默认是 escape,但万一你用了旧版或自定义了行为
2. 没禁用危险协议,比如 javascript:data:text/html 这种
3. 没处理内联事件,比如 onerroronloadonclick,这些在 img、iframe、svg 里都能埋雷
4. 可能后端没再做一层清洗,直接存了原始 dirty HTML

正确姿势是这样配置 sanitize-html

const options = {
allowedTags: ['p', 'img', 'br', 'div', 'strong', 'em', 'a', 'h1', 'h2', 'h3', 'ul', 'ol', 'li'],
allowedAttributes: {
'a': ['href', 'title', 'target'],
'img': ['src', 'alt', 'title'],
'div': ['class', 'style'],
'p': ['class', 'style']
},
allowedIframeDomains: [], // 如果用 iframe,限制域名
allowedSchemes: ['http', 'https', 'mailto', 'data'], // data 只允许图片 base64,别乱开
allowedSchemesByTag: {
'img': ['http', 'https', 'data'],
'a': ['http', 'https', 'mailto']
},
allowedSchemesAppliedToAttributes: ['href', 'src', 'action', 'formaction'],
allowProtocolRelative: false,
disallowedTagsMode: 'escape', // 确保 script、style 等被转义而不是删掉(删了也可能被绕过)
transformTags: {
'img': (tagName, attribs) => {
// 强制加 rel="noopener noreferrer" 防止 window.opener 攻击
if (attribs.target === '_blank') attribs.rel = 'noopener noreferrer';
return { tagName, attribs };
}
}
};

const clean = sanitizeHtml(dirty, options);


但重点来了:别只靠前端 sanitize!
后端一定要用类似 DOMPurify 的库(Node 环境可以用 jsdom + DOMPurify)再过一遍,或者用 Java 的 Jsoup、Python 的 bleach,确保入库前是干净的。

另外补充一点:如果你用 Quill,它默认会把 HTML 存成 Delta 格式,但很多人为了方便直接用 editor.getHTML(),这个返回的就是原始 HTML,千万别直接存这个,应该用 editor.getContents() 得到 Delta,再转成 HTML 时用 Delta.toHTML(),但这个方法本身也不安全,还是得再 sanitize。

最后说个血泪教训:
XSS 防御永远是“纵深防御”,前端加后端双保险,再配合 CSP header,三重防护才稳妥。

附个 CSP 示例(Nginx 里加):

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';


这样就算漏了点,脚本也跑不起来。
浏览器兼容性基本都 OK,IE 那种老古董不考虑也罢。
点赞 4
2026-02-27 10:07
Zz雨妍
Zz雨妍 Lv1
你的问题出在对 sanitize-html 的配置上,虽然你定义了白名单标签和属性,但没有处理嵌套的恶意标签。攻击者可以通过将脚本嵌套在合法标签中来绕过过滤规则。比如你提到的 <div><script>alert(1)</script></div><script> 标签被保留了下来。

要彻底阻断 XSS 攻击,需要调整配置并加入额外的防护措施。首先,明确一点:sanitize-html 默认不会移除 <script> 标签,除非你在 allowedTags 中显式禁止它。其次,仅仅依赖白名单是不够的,还需要结合上下文进行更严格的校验。

以下是改进后的代码示例:

const sanitizeHtml = require('sanitize-html');

const options = {
allowedTags: ['p', 'img', 'br', 'div'], // 明确允许的标签
allowedAttributes: {
'img': ['src'] // 允许 img 标签的 src 属性
},
disallowedTagsMode: 'remove', // 将未允许的标签直接移除,而不是转义
allowVulnerableTags: false, // 禁用潜在危险标签(如 script)
};

// 示例输入
const dirty = '<div><script>alert(1)</script></div>';
const clean = sanitizeHtml(dirty, options);

console.log(clean); // 输出:<div></div>


这里的关键点在于:
- 设置 disallowedTagsMode'remove',确保所有不在白名单中的标签被完全移除。
- 确保 allowVulnerableTags 设置为 false,以防止意外允许危险标签。

此外,光靠前端或存储前的过滤是不够的,建议在渲染时也做一层防护。比如在输出到页面时,使用安全的编码方式。如果你用的是模板引擎,可以启用自动转义功能;如果是手动拼接 HTML,可以用类似 DOMPurify 的库进一步净化内容。

最后提醒一下,XSS 防护是个系统工程,除了输入过滤,还要注意 HTTP 响应头的安全配置,比如设置 Content-Security-Policy 来限制脚本执行来源。这样即使有漏网之鱼,也能通过 CSP 阻止恶意脚本运行。

总之,配置好 sanitize-html 是第一步,后续还要结合多层防御机制才能真正降低风险。安全这东西,永远别想着一劳永逸,得时刻保持警惕。
点赞 11
2026-02-17 17:00