htmlspecialchars()是防xss最简但易错的防线,必须显式传ent_quotes和'utf-8'参数,且仅适用于html文本/属性上下文,其他场景(js、url、富文本)需分别用json_encode()、urlencode()、dompurify等专用方案。

直接输出用户输入时,htmlspecialchars() 是最简但易错的防线
只要用户输入会原样插入 HTML 文本内容(比如评论、昵称、搜索关键词回显),就必须对 、<code>>、&、"、' 做实体编码。PHP 的 htmlspecialchars($input, ENT_QUOTES, 'UTF-8') 是最低成本方案,但它只管“HTML 内容上下文”——一旦你把它用在属性值、JavaScript 字符串或 URL 里,就完全失效。
常见错误包括:
- 对
title="用户输入"直接套htmlspecialchars(),却没处理双引号本身被闭合后注入后续属性(如user" onclick="alert(1)) - 把用户输入拼进
location.href = "xxx?val="+user,此时需的是 URL 编码,不是 HTML 实体编码 - 用
ENT_HTML401而非ENT_QUOTES,导致单引号不转义,在属性中构成突破点
strip_tags() 不等于安全,它无法拦截属性级 XSS 向量
strip_tags($input, ['p', 'br', 'strong']) 看似干净,实则危险:它只删标签,不碰属性。攻击者仍可提交 <p onmouseover="fetch('/steal?c='+document.cookie)">悬停我</p> —— 标签在白名单里,事件属性毫发无损。
真正需要富文本时,别自己写正则过滤;优先用 DOMPurify.sanitize(),它基于真实 HTML 解析器重建 DOM,能识别并剥离 onerror、javascript:、data:text/html 等所有已知绕过模式。
关键配置项必须显式设置:
-
ALLOWED_TAGS和ALLOWED_ATTR严格白名单,禁用style除非你同步启用 CSS 过滤 - 开启
FORBID_CONTENTS拦截<script></script>、<object></object>等高危节点 - 禁用
ADDITIVE_SCHEMA,避免扩展默认规则引入未知风险
DOM 型 XSS 最难防,根源在 innerHTML 和 document.write()
服务端过滤再严,只要前端用 element.innerHTML = userContent 或 document.write(location.hash.slice(1)),就等于主动执行不可信代码。这类漏洞不依赖服务端存储,纯靠客户端解析逻辑缺陷触发。
替代方案明确且有限:
- 用
textContent替代innerHTML渲染纯文本 - 若必须插 HTML,走
DOMPurify.sanitize()+element.appendChild(),绝不拼字符串 - 禁止使用
eval()、Function()、setTimeout(string)动态执行用户输入 - URL 参数解析后,先校验是否为合法路径或 ID,再用于 DOM 操作,而非直接反射
CSP 是最后一道网,但配置不当等于形同虚设
Content-Security-Policy: default-src 'self' 看似保险,实际放行了所有内联脚本和 eval——攻击者只需注入 <script>alert(1)</script> 就能绕过所有前端过滤。
有效 CSP 必须收紧到最小权限:
- 禁用
unsafe-inline和unsafe-eval,改用nonce或hash白名单机制 - 对动态加载的资源(如用户头像、富文本图片)单独设
img-src,不继承default-src - 配合
report-uri收集违规日志,而不是仅靠Content-Security-Policy-Report-Only长期观望 - 注意浏览器兼容性:
worker-src在旧版 Safari 不生效,需额外降级策略
真正棘手的从来不是“该不该加”,而是不同上下文(HTML、JS、CSS、URL)需要各自独立的转义逻辑,而开发者常默认它们可以复用同一套过滤函数。这点一旦忽略,前面所有努力都可能被一个 onload= 属性或一段未编码的 JSON 插入击穿。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











