富文本渲染必须服务端双重净化,前端dompurify仅防手滑;入库前用htmlpurifier白名单过滤,渲染前再次校验,禁止依赖strip_tags()或htmlspecialchars(),字段命名需区分raw与safe。

富文本提交后直接 innerHTML 渲染 = 开门放攻击者进来
后台管理系统里,只要用 innerHTML 或 v-html、dangerouslySetInnerHTML 渲染未处理的富文本字段,就等于把数据库里的 <script></script>、onerror、javascript: 全部交给浏览器执行。这不是“可能被攻破”,而是“必然执行”。你看到的预览正常,不代表真实入库和渲染安全——因为预览走的是前端 DOMPurify,而真实请求可以绕过它直接 POST 原始 HTML。
实操建议:
- 所有富文本字段入库前,必须经服务端净化,不能依赖前端任何过滤逻辑
- 禁止在模板中写
{{ rawHtml }}或<div v-html="rawHtml"></div>,除非该变量已明确来自后端净化后的字段 - 若用 Vue,
v-html绑定的值必须是后端返回的safe_html字段,而非原始content - 若用 React,
dangerouslySetInnerHTML的__html值必须由后端提供,且该值已通过 HTMLPurifier 或等效白名单过滤
PHP 后端别用 strip_tags() 或 htmlspecialchars() 处理富文本
strip_tags() 只删标签不清理属性,<img src="x" onerror="alert(1)"> 会被放过;htmlspecialchars() 把整个字符串当纯文本转义,<p><strong>加粗</strong></p> 就变成一堆源码,用户看到的是 HTML 标签字面量,不是加粗效果。
实操建议:
- 必须用
HTMLPurifier(推荐)或XssHtml类做最终净化 -
HTMLPurifier配置必须显式设置:'HTML.Allowed' => 'p,b,i,u,br,a[href|target],img[src|alt|width|height]',不能只靠默认配置 - 禁用危险协议:
'URI.AllowedSchemes' => ['http', 'https', 'mailto'],否则href="javascript:..."会漏掉 - 图片样式如
background-size默认被清空,需手动开启'CSS.AllowTricky' => true并加入'CSS.AllowedProperties'白名单
前端 DOMPurify 不是安全方案,只是防手滑
DOMPurify 在用户粘贴时调用一次 DOMPurify.sanitize(),确实能干掉肉眼可见的 <script></script>。但它拦不住 curl、Postman、禁用 JS 后填表单、拖放粘贴等绕过方式。它的作用仅限于提升编辑体验,不是安全边界。
实操建议:
- 表单
submit事件中可轻量调用DOMPurify.sanitize()用于本地预览,但原始值仍要原样发给后端 - 不要监听
input、paste或keydown做实时过滤——性能差、覆盖不全、易被绕过 - 校验链接属性必须单独调用
DOMPurify.isValidAttribute('a', 'href', value),不能只靠标签白名单 - Vue/React 中,即使用了 DOMPurify,也要确保绑定的变量来源可信,不能把用户输入的原始字段直接传进去
入库和渲染两个环节都得做净化,缺一不可
只在入库时净化、渲染时直接输出,风险在于:如果后续有其他接口(比如导出 Excel、API 返回原始字段、第三方系统对接)绕过净化逻辑,恶意内容就会再次暴露;反之,只在渲染时净化,意味着恶意 HTML 已存进数据库,污染了数据源头,排查和清理成本极高。
实操建议:
- 入库前:调用
HTMLPurifier->purify($raw),保存净化后结果到数据库字段(如content_safe) - 渲染前:仍需再跑一次净化(哪怕只是轻量校验),防止字段被误更新或跨库同步引入脏数据
- 对历史数据批量清洗,不能只靠“以后注意”,要用脚本统一过一遍
HTMLPurifier - 富文本字段命名要有区分度,比如
content_raw(仅调试用)、content_safe(生产渲染用),避免混淆
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











