必须用 htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8):缺 ENT_QUOTES 无法转义单引号,缺 UTF-8 可能导致多字节截断绕过,htmlentities() 会破坏 emoji 和外文且不解决核心风险。

所有用户数据输出到 HTML 页面前,必须用 htmlspecialchars() 显式转义;不转义就等于把执行权直接交给攻击者。
为什么 htmlspecialchars() 必须带 ENT_QUOTES 和 UTF-8 参数
缺参数的调用(比如 htmlspecialchars($str))在真实场景中极易被绕过:
- 不传
ENT_QUOTES:单引号不会被转义,<img src="x" onerror="alert(1)">里的onerror仍可触发 - 不指定编码(如
'UTF-8'):PHP 可能按 ISO-8859-1 解析,遇到多字节字符时发生截断,导致%E0%80%80<script>类绕过 - 用
htmlentities()替代:会把中文、emoji 全部转成实体,破坏内容可读性,且不解决单引号问题
正确写法只有这一种惯用组合:htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8')。
输出位置不同,转义函数必须换
把 htmlspecialchars() 塞进所有地方是常见错误。它只对 HTML 文本节点和属性值安全,其他上下文完全无效:
立即学习“PHP免费学习笔记(深入)”;
- 插进
<script>var msg = "<?php echo $user; ?>";</script>:必须用json_encode($user, JSON_UNESCAPED_UNICODE),且外层双引号要匹配 - 拼在 URL 里,如
<a href="page.php?name=<?php echo $user; ?>">:得用urlencode($user),htmlspecialchars()会把&变成&,破坏参数解析 - 写进
onclick="doSomething('<?php echo $user; ?>')":属性值虽有引号,但 JS 执行环境已切换,仍需json_encode()或至少addslashes()+ 外层引号严格配对
富文本不能靠 htmlspecialchars() 或 strip_tags()
论坛、商品描述等需要保留部分标签的场景,htmlspecialchars() 会把所有 <p>、<strong> 都干掉,strip_tags() 又无法过滤 <img src="x" onload="alert(1)"> 这类带危险属性的标签。
必须用白名单机制的净化库:
- 安装
ezyang/htmlpurifier(Composer) - 配置只允许
<p>、<strong>、<a href>(且href必须以https://开头) - 禁用所有事件属性(
onload、onerror、onclick等)和javascript:协议
别信“正则删 script 标签”或 “DOMDocument + 循环删属性”——XSS payload 有几十种嵌套、注释、编码混淆变体,手工方案必漏。
CSP 是最后一道兜底防线,但 header 配置有坑
即使某处 echo 忘了转义,CSP 也能拦住大部分脚本执行。但配置错等于没开:
-
Content-Security-Policy: default-src 'self'不够——它不限制内联脚本,<script>alert(1)</script>仍可执行 - 必须加
script-src 'self' 'unsafe-inline'才允许内联,但这就废了 CSP 的主要价值;更安全的是去掉'unsafe-inline',改用nonce或hash -
X-Content-Type-Options: nosniff要配,否则浏览器可能把text/plain响应当 JS 执行,绕过 MIME 类型检查 -
X-XSS-Protection: 1; mode=block已被现代浏览器弃用,优先用 CSP
真正容易被忽略的是:CSP 只对响应体生效,如果 PHP 输出了未闭合的 <script> 标签,或者前端 JS 动态拼接 HTML(如 el.innerHTML = userHtml),CSP 也救不了——防御必须从输出源头卡死。



















