php防xss的关键是输出时按上下文严格转义:html文本和属性值须用htmlspecialchars()显式传参,禁用无参调用、script内直输、strip_tags()等错误方式。

PHP防XSS不是靠“过滤输入”,而是靠「输出时按上下文严格转义」。不加参数的htmlspecialchars()、在<script></script>里直接echo用户数据、用strip_tags()代替上下文感知处理——这些都会让XSS照常触发。
HTML文本和属性值必须用htmlspecialchars()显式传参
只要用户数据会出现在HTML标签内(如<div><?php echo $user_input; ?></div>)或双引号包裹的属性中(如<input value="<?php echo $val; ?>">),就必须调用htmlspecialchars(),且三个参数一个不能少:
-
ENT_QUOTES | ENT_HTML5:确保单引号、双引号、HTML5自定义数据属性都安全转义 -
'UTF-8':显式声明编码,避免多字节截断绕过(PHP 7.4+虽有默认,但不显式写等于埋雷) - 别用
htmlentities():它会把中文、emoji全转成实体,破坏内容可读性 - 禁用
ENT_NOQUOTES:单引号不转义,<img src="x" onerror="alert(1)">就直接执行
<script></script>内嵌JS字符串必须用json_encode()带hex flags
下面这种写法是高危的:<script>var msg = "<?php echo $user_input; ?>";</script>。此时htmlspecialchars()完全无效——浏览器把引号内的内容当JS字符串解析,不是HTML。
正确做法是:
- 用
json_encode($user_input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) - 确保外层JS字符串用双引号包裹(
"<?php echo json_encode(...); ?>"),否则JSON生成的\u0022可能被误解析 - 绝对不要用
addslashes()或str_replace()模拟转义——JS解析器不吃这套
富文本不能靠htmlspecialchars(),得用HTMLPurifier白名单过滤
论坛、商品描述、后台编辑器这类场景,用户确实需要发<b></b>、<a href></a>等合法标签。这时候htmlspecialchars()会把所有格式干掉,strip_tags()又拦不住<svg onload="..."></svg>或注释绕过(<!--<script>-->)。
可行方案只有:
- 用Composer安装
HTMLPurifier,配置只允许p、strong、a[href]等必要标签和属性 - 禁止
on*事件、javascript:协议、data:协议等危险模式 - 别自己写正则过滤——HTML结构太复杂,正则永远漏
CSS内联样式和URL字段要单独校验,不能无脑转义
像<div style="color: <?php echo $color; ?>;">或<code><a href="<?php%20echo%20%24url;%20?>"></a>这种输出点,htmlspecialchars()只能防引号闭合,防不了expression(...)或javascript:alert()。
应对方式是:
- 颜色值用正则校验:
preg_match('/^#([0-9A-F]{3}){1,2}$/i', $color),不匹配就拒用 - URL先用
filter_var($url, FILTER_VALIDATE_URL),再检查parse_url($url, PHP_URL_SCHEME)是否为http或https - 数据库存原始值,但渲染前必须走一遍对应上下文的校验或转义——存储型XSS的根子不在入库,而在输出失控
最易被忽略的一点:模板引擎(如Blade、Twig)的|raw或{!! $var !!}是XSS快捷通道,哪怕只在一个地方加了,整页防御就归零。别信“我只在这儿显示富文本”,得确认每个echo、每个innerHTML插入点,都匹配了它所处的上下文规则。











