ssr渲染中html非法字符过滤必须在服务端输出前精准完成,htmlspecialchars()需三参数调用且仅用于纯文本上下文;富文本须用htmlpurifier白名单配置净化;v-html等危险指令必须依赖服务端净化结果,客户端二次处理无效。

SSR 渲染中对 HTML 文档做非法字符过滤,不能靠前端或模板层“顺手一过”,必须在服务端输出前完成且上下文精准——否则要么漏掉 XSS,要么把合法内容(如 ©、€、数学符号)转成乱码或空格。
htmlspecialchars() 在 SSR 中的正确调用方式
它只适用于纯文本插入场景,比如 <p>{{ content }}</p> 里的 content。错误写法 htmlspecialchars($input) 缺少参数,单引号不转、编码未指定,极易被 GBK 双字节截断绕过。
- 必须传三参数:
htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8') - 仅限插入到 HTML body 文本、
title、textareavalue 等纯文本上下文 - 绝不能用于
href、onclick、style属性值,那里需要 URL 编码或 CSS 属性校验 - 若已用
json_encode()输出到 JS 变量,再喂给htmlspecialchars()会把&变成&,显示异常
富文本字段在 SSR 中必须走 HTMLPurifier
strip_tags() 或正则删 <script></script> 在 SSR 场景下等于没防——用户提交 <p onclick="alert(1)">点我</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher"><img
src="https://img.php.cn/upload/skill/000/000/081/179109368394970.jpg" alt="Wechat HTML Publisher" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher" class="overflowclass">Wechat HTML Publisher</a>
<p class="overflowclass">直接上传HTML富文本到微信公众号草稿箱。支持完整的HTML格式,无需Markdown转换。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>,strip_tags() 只删标签名,属性全保留;正则根本解析不了注释包裹的脚本或 SVG onload。
- 必须显式配置白名单:
HTML.Allowed = 'a[href|title],p,br,strong,em,ul,ol,li' - 禁用宽松默认:
HTMLPurifier_Config::createDefault()不可用,要手动构建 config - 协议必须收紧:
'URI.AllowedSchemes' => ['http', 'https', 'mailto'],否则javascript:、data:仍可执行 - 入库前净化一次即可,渲染时不要再调用——重复净化可能把
©变成©再变成乱码
SSR 模板里 v-html / dangerouslySetInnerHTML 的陷阱
Vue 的 v-html 和 React 的 dangerouslySetInnerHTML 是 SSR 输出链上最危险的出口。哪怕你前端用 DOMPurify 做了预处理,只要后端返回的 HTML 字符串未经服务端净化,攻击者 curl 一个 POST 就能绕过所有前端逻辑。
- 变量来源必须可信:只允许来自数据库已净化字段,禁止拼接用户原始输入
- 不要在 SSR 模板里写
<div v-html="userInput"> —— 即使加了 <code>v-safe-html指令,指令本身运行在客户端,不可信 - 若必须动态插入,确保后端返回的是
DOMPurify.sanitize(...)后的字符串,且该调用发生在 PHP/Node.js 层,不是浏览器 - Vue 项目中,
@click、@mouseover等事件绑定也需校验:用户输入的data-id若含单引号或换行,可能破坏内联事件结构
真正容易被忽略的点是:SSR 渲染的 HTML 是一次性生成并发送给浏览器的,所有过滤必须在响应写出前完成。任何依赖客户端 JS 的“二次净化”都无效,而模板引擎的“自动转义”开关常被默认关闭或仅作用于变量插值,对 v-html 类指令完全不生效。










