必须用 htmlspecialchars() 处理所有用户数据输出,推荐 htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');JS 字符串用 json_encode();富文本用 HTMLPurifier;设置 CSP 和 X-Content-Type-Options 增强防御。

输出前必须用 htmlspecialchars() 处理用户数据
PHP 本身不自动转义输出内容,所有从 $_GET、$_POST、$_COOKIE、数据库、文件、第三方 API 拿来的字符串,只要会渲染到 HTML 页面里,就必须过一遍 htmlspecialchars()。这是最直接、最不可绕过的防线。
常见错误是只在表单提交时“过滤输入”,却忘了输出才是 XSS 触发点。比如:<div><?php echo $_GET['name']; ?></div> —— 这行代码只要传入 name=<script>alert(1)</script> 就直接执行脚本。
-
htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8')是推荐写法:明确指定编码,兼容 HTML5,同时转义单双引号 - 不要用
htmlentities()替代 —— 它会把非 ASCII 字符也转成实体,可能破坏中文、emoji 或其他 UTF-8 内容 - 如果输出场景是 JavaScript 字符串(比如
var msg = "<?php echo $user_input; ?>";),htmlspecialchars()不够用,得用json_encode($str, JSON_UNESCAPED_UNICODE)并确保外层引号匹配
富文本内容不能只靠 htmlspecialchars(),要用专用库白名单过滤
用户需要发带格式的文本(比如论坛帖子、商品描述)时,htmlspecialchars() 会把所有标签都干掉,体验崩坏。这时候不能手写正则去“删掉 script 标签”,而应交给成熟白名单过滤器。
推荐用 HTMLPurifier(需 Composer 安装),它按 W3C 规范解析 HTML,只保留你允许的标签和属性。例如只允许 <p></p>、<strong></strong>、<a href></a>(且 href 必须以 http:// 或 https:// 开头)。
立即学习“PHP免费学习笔记(深入)”;
- 别信“用 strip_tags() + 白名单正则”这种 DIY 方案 —— 正则无法可靠解析嵌套、注释、编码混淆(如
<script></script>)等 XSS 变体 -
DOMDocument+ 手动遍历节点也容易漏判,比如<img src="x" onerror="alert(1)" alt="PHP怎么防止XSS跨站脚本攻击_PHP XSS防护措施详解【详解】" >中的事件属性 - 若用 CKEditor、TinyMCE 等编辑器,后端仍需独立过滤 —— 前端配置可被绕过
设置 HTTP Header 防御纵深:X-Content-Type-Options 和 Content-Security-Policy
Header 不是银弹,但能拦住不少低级 XSS 利用方式,尤其当开发者某处漏了转义时。
X-Content-Type-Options: nosniff 强制浏览器按声明的 Content-Type 解析响应,防止 MIME 类型混淆攻击(比如把一个 JS 文件伪装成 text/plain 绕过某些检查)。
Content-Security-Policy 更关键。例如设为 default-src 'self'; script-src 'self' 'unsafe-inline' https:,就能禁止内联脚本(<script>...</script> 和 onclick=...)和非同源 JS 加载 —— 即使 HTML 里混进了恶意标签,浏览器也不会执行。
-
'unsafe-inline'要谨慎:Vue/React 的内联模板、某些统计代码可能依赖它;生产环境建议用nonce或hash精确控制 - PHP 中用
header('Content-Security-Policy: ...');设置,注意避免重复 header 导致策略失效 - CSP 报告模式(
Content-Security-Policy-Report-Only)适合灰度期收集违规行为,但别长期开着不处理
$_SERVER['PHP_SELF'] 直接拼接进 form action 是经典 XSS 入口
很多老教程教这么写:<form action="<?php%20echo%20%24_SERVER['PHP_SELF'];%20?>"></form>。但 $_SERVER['PHP_SELF'] 包含当前请求 URI 路径,攻击者访问 /search.php/%22%3E%3Cscript%3Ealert(1)%3C/script%3E 就能让页面变成:<form action="/search.php/">
<script>alert(1)</script>"></form>。
- 修复方法很简单:用
htmlspecialchars($_SERVER['PHP_SELF']),或者更稳妥地,直接写死 action(如action="/search.php") - 同理,
$_SERVER['QUERY_STRING']、$_SERVER['REQUEST_URI']都不能未经转义就输出到 HTML - 框架用户(如 Laravel、Symfony)通常已封装好这些变量的自动转义,但自定义模板或原生 PHP 项目必须手动盯紧
真正难防的是上下文混淆:同一个变量,在 HTML 文本、HTML 属性、JavaScript 字符串、CSS、URL 里需要不同的转义方式。别指望一个函数打天下,得看它最终落在哪一层。这也是为什么 CSP 和输出转义要配合着用 —— 一层防不住,还有下一层。



















