动态SSR渲染放大安全风险:属性注入因引号闭合+事件绑定更危险,富文本须服务端白名单净化,SSR输出必须为严格闭合的纯fragment以避免hydration静默失败。

动态 SSR 渲染本身不增加安全性,反而放大风险——服务端拼出的 HTML 一旦含未转义用户数据,就直接绕过前端所有防护,且 hydration 失败常被误判为“样式问题”而忽略。
属性值注入:为什么 data-id="{{ userInput }}" 比 <div>{{ userInput }}</div> 更危险
属性上下文允许引号闭合 + 事件绑定,浏览器解析时会提前截断并执行后续 JS。例如 userInput = '" onmouseover="alert(1)" x="' 插入到 <div data-id="{{ userInput }}"> 中,最终生成 <div data-id="" onmouseover="alert(1)" x="">,onmouseover 立即触发。
- EJS/Nunjucks 等模板中,
不转义,是显式关闭转义,务必避免 - Vue SSR 中
:data-id="userInput"安全(自动调用escapeAttribute),但data-id="{{ userInput }}"危险(纯字符串插值) - Node.js 的
he.escape()默认只处理<、>、&,对引号无效;必须用he.escapeAttribute()或手动input.replace(/"/g, """).replace(/'/g, "'")
富文本渲染:为什么服务端净化不能省,客户端再跑一遍 DOMPurify 反而有害
服务端吐出已清洗的 HTML,客户端又用 DOMPurify 二次处理,不仅浪费 CPU,还可能因规则不一致导致标签被误删(如服务端允许 <figure>,客户端配置漏掉),引发 hydration 差异或样式错乱。
- 服务端必须用白名单净化:Node.js 推荐
sanitize-html,配置allowedTags: ['p', 'br', 'strong', 'a']、allowedAttributes: { a: ['href'] }、allowedSchemes: ['http', 'https', '/'] - 客户端渲染前仍需
DOMPurify.sanitize(),但配置必须和服务端完全一致,否则 hydration 失败 - 禁用黑名单模式:
FORBID_TAGS: ['script']无效,DOMPurify默认已禁用高危标签,重点是显式声明ALLOWED_TAGS
SSR 输出结构完整性:为什么多一个 <html> 或少一个 </div> 就导致 hydration 静默失败
React/Vue hydration 依赖服务端输出的 DOM 树与客户端重建结果严格一致。若 SSR 返回的 HTML 含多余根标签、未闭合标签或非法嵌套,客户端解析后 AST 结构偏移,组件将放弃复用而整树重挂载——页面闪动、状态丢失、性能骤降,但控制台无报错。
立即学习“前端免费学习笔记(深入)”;
- SSR 输出必须仅为纯 fragment:禁止包含
<html>、<head>、<body>,只输出<div id="app">...</div>内容 - 使用
parse5或DOMParser预检:解析后比对doc.body.innerHTML是否与原始字符串一致,不一致则说明存在未闭合或嵌套错误 - 禁用模板引擎的空格压缩(如 EJS 的
而非 <code>),避免服务端少个空格导致属性顺序错位
最易被忽略的是状态注入环节:把 JSON 塞进 <script>window.__INITIAL_STATE__ = ...</script> 时,若没对 '、"、</script> 做双重转义,脚本会提前截断,hydration 直接失效——这不是 XSS,却是更隐蔽的运行时崩坏。



















