本文目录导读:

PHP跨站脚本攻击(XSS)是最常见的安全漏洞之一,防御XSS攻击的核心原则是:永远不要信任用户的输入,对所有输出进行编码。
下面我将从防御策略、具体代码示例和进阶防护三个层面,为你提供一套完整的防御方案。
核心防御策略(三大原则)
- 输入过滤(Input Validation):验证用户输入的数据格式(如邮箱、数字),拒绝不符合预期的内容。
- 输出编码(Output Escaping):将用户数据输出到HTML上下文时,转义特殊字符(如
<,>, , ,&)。 - 内容安全策略(CSP):通过HTTP头限制浏览器只执行白名单内的脚本,即使攻击脚本被注入也无法执行。
具体代码实现(分场景防御)
场景1:输出到HTML标签内容之间
危险代码:
echo "<div>用户评论:".$_POST['comment']."</div>";
如果用户输入 <script>alert(1)</script>,攻击就会执行。
安全做法:使用 htmlspecialchars() 转义。
echo "<div>用户评论:".htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8')."</div>";
参数说明:
ENT_QUOTES:同时转义单引号和双引号。'UTF-8':指定字符集,防止宽字节绕过。
场景2:输出到HTML属性值中
危险代码:
echo "<img src='".$_GET['img']."' />";
如果用户输入 ' onerror='alert(1),属性注入就发生了。
安全做法:不仅需要转义,还需要验证协议。
$url = $_GET['img'];
if (filter_var($url, FILTER_VALIDATE_URL) && preg_match('#^https?://#i', $url)) {
// 只允许 http 或 https 开头的 URL
echo "<img src='".htmlspecialchars($url, ENT_QUOTES, 'UTF-8')."' />";
} else {
echo "非法 URL";
}
场景3:输出到JavaScript上下文
危险代码:
echo "<script>let user='".$_GET['name']."';</script>";
如果用户输入 '; alert(1); ',脚本会逃逸出字符串。
安全做法:绝不在JS中直接拼接PHP变量,推荐使用 json_encode() 将数据转为 JSON 格式。
$data = ['name' => $_GET['name']]; echo "<script>let userData = ".json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT).";</script>";
关键点:JSON_HEX_* 选项会将特殊字符转为 Unicode 十六进制,防止JS解析器误解。
场景4:输出到URL中
危险代码:
echo "<a href='".$_GET['page']."'>跳转</a>";
如果用户输入 javascript:alert(1),会执行JS。
安全做法:使用 filter_var 过滤,或使用白名单。
进阶防护:内容安全策略(CSP)
设置HTTP响应头,告诉浏览器只信任来自特定源的脚本。
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
解释:
default-src 'self':所有资源默认只能从同源加载。script-src 'self':脚本只能从同源加载(禁止内联脚本和eval)。- 如果使用了前端框架(如Vue/React),可能需要适当放宽
style-src。
完整防御函数封装(推荐使用)
建议将输出编码封装为全局函数,避免遗漏。
// 专用于 HTML 上下文输出
function h($string) {
return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
}
// 专用于 URL 属性输出
function u($string) {
return urlencode($string ?? '');
}
// 专用于 JS 上下文输出
function j($string) {
return json_encode($string, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
// 使用示例
echo "<div>" . h($_POST['name']) . "</div>";
echo "<a href='?page=" . u($_GET['page']) . "'>链接</a>";
echo "<script>const data = " . j($userData) . ";</script>";
其他重要防护措施
-
设置
HttpOnlyCookie:防止XSS攻击窃取用户会话(Session)。session_set_cookie_params(['httponly' => true]);
-
关闭
register_globals(PHP 8 已移除):确保所有变量都通过$_GET/$_POST/$_SESSION获取。 -
模板引擎自动转义:如果使用 Twig 或 Blade 模板引擎,默认开启自动转义(如 Twig 的
{{ var }}默认转义)。 -
使用现成的安全库(如
HTML Purifier)处理富文本(允许用户输入<b>、<i>等白名单标签):composer require ezyang/htmlpurifier
$purifier = new HTMLPurifier(); $clean_html = $purifier->purify($dirty_html);
常见绕过手段提醒(注意这些坑)
- 字符编码问题:必须指定
UTF-8,防止使用GBK等编码绕过。 - 过滤了
<script>但未过滤事件属性(如<img onerror>):htmlspecialchars能防住这种攻击。 - 未对
javascript:协议过滤:仅对 URL 和属性进行编码不够,必须加白名单协议。
最终建议清单
| 开发场景 | 必须做的操作 |
|---|---|
| 任何PHP动态输出 | 使用 htmlspecialchars() 或 h() 函数 |
| 富文本显示 | 使用 HTMLPurifier 白名单过滤 |
| 链接跳转 | 验证协议(http/https),拒绝 javascript: |
| AJAX/JSON 返回 | 使用 json_encode() 且开启 JSON_HEX_* |
| 所有页面 | 设置 CSP 响应头,全站 HTTPS |
“输入过滤”和“输出编码”是两回事。 防御XSS的重点是输出编码,因为你需要允许用户“合理”地输入 < 或 >(比如数学公式),但在输出时将其转为无害文本。
如果你使用的是现代框架(如 Laravel、Symfony),它们自带 e() 辅助函数和模板转义,但原生 PHP 开发时必须严格遵循上述规则。