PHP中XSS防护核心是按上下文精准转义:HTML输出用htmlspecialchars(ENT_QUOTES|ENT_HTML5),JS变量用json_encode(),富文本用HTMLPurifier白名单过滤,且CSP/HttpOnly仅为兜底。

直接输出用户数据到 HTML 页面,不加转义,htmlspecialchars() 漏用或错用,是 phpEnv 环境下 XSS 漏洞最常见、最直接的成因。不是“要不要做”,而是“在哪做、怎么选参数、漏了哪一环”。
所有 echo / print 输出用户数据前必须过 htmlspecialchars()
phpEnv 本身不自动转义,$_GET、$_POST、$_COOKIE、数据库查出的字段,只要进 HTML 流程,就必须手动转义。
- 错误写法:
echo '<div>' . $_GET['q'] . '</div>';—— 传入q=<script>alert(1)</script>直接执行 - 正确写法:
echo '<div>' . htmlspecialchars($_GET['q'], ENT_QUOTES | ENT_HTML5, 'UTF-8') . '</div>'; -
ENT_QUOTES必须带上:否则单引号在属性值中(如value='xxx')可能被绕过 -
ENT_HTML5推荐加上:兼容 HTML5 实体命名(如'),避免旧模式下对某些字符处理异常 - 别用
htmlentities()替代:它会把中文、emoji 全部转成XXXX;,破坏显示且不可逆
json_encode() 是输出到 JavaScript 的唯一安全方式
当用户数据要进 <script></script> 块、onclick、data- 属性或 Ajax 返回 JSON 时,htmlspecialchars() 不够用,浏览器仍可能解析出恶意 JS。
- 危险写法:
var name = "<?php echo $_GET['name']; ?>";—— 双引号内插"或\就崩 - 安全写法:
var name = <?php echo json_encode($_GET['name'], JSON_UNESCAPED_UNICODE); ?>; - 注意外层引号匹配:如果 JS 中用单引号包裹,
json_encode()输出的双引号字符串依然合法;反之亦然 - 不要自己拼
"' + escape(...) + '":JS 端escape()已废弃,且无法覆盖所有边界情况
富文本内容不能靠 htmlspecialchars(),得用白名单过滤器
论坛、文章编辑器等场景允许 <p>、<strong>,但禁用 <script>、onerror、javascript:。这时 htmlspecialchars() 会把所有标签干掉,strip_tags() 又太弱——它不处理属性、注释、编码混淆等 XSS 变体。
立即学习“PHP免费学习笔记(深入)”;
- 必须用
HTMLPurifier:通过 Composer 安装,按 W3C 规范解析 DOM,只保留你明确允许的标签和属性 - 示例配置片段:
$config->set('HTML.Allowed', 'p,strong,em,a[href|title],img[src|alt]');,并强制a[href]只能以http://或https://开头 - 禁止手写正则过滤:比如
preg_replace('/<script.>.*?/is', '', $input)</script.>—— 会被<scr>ipt></scr>或注释绕过 - 前端编辑器(如 CKEditor)的配置不可信:攻击者可绕过前端直接 POST 原始 HTML,后端必须独立过滤
CSP 和 HttpOnly 是兜底防线,但不能替代输出转义
phpEnv 下可通过 header() 或 Apache/Nginx 配置添加响应头,它们在某处漏转义时能拦住部分利用,但绝非替代方案。
- CSP 示例:
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'");—— 注意'unsafe-inline'会削弱防护,应尽量移除,改用外部 JS 或 nonce -
X-Content-Type-Options: nosniff必加:防止浏览器把text/plain响应误当成 JS 执行 - Cookie 必须设
HttpOnly:setcookie('sessid', $val, [...], ['httponly' => true, 'secure' => true]);—— 这样即使 XSS 成功,也无法用document.cookie窃取 - 这些头对 DOM 型 XSS 几乎无效:因为脚本根本没走网络请求,纯客户端触发
最易被忽略的是输出上下文切换:同一段用户数据,可能既进 HTML 正文、又进 JS 变量、还进某个属性值。每个位置都要按对应规则处理,不能“统一过一遍 htmlspecialchars()”就完事。转义不是装饰,是每处输出前的强制检查点。



















