htmlspecialchars() 是第一道也是最关键的防线,因为 xss 攻击发生在浏览器渲染 html 阶段,而 php 不会自动转义输出;仅过滤输入(如 strip_tags())无法阻止未转义的用户数据在输出时被解析为恶意脚本,必须在每次输出到 html 上下文前调用 htmlspecialchars($str, ent_quotes | ent_html5, 'utf-8') 进行上下文精准编码。

必须在输出时用 htmlspecialchars() 处理所有用户数据,其他环节的“过滤输入”不能替代这一步。
为什么 htmlspecialchars() 是第一道也是最关键的防线
PHP 不会自动转义输出内容。只要把 $_GET、$_POST、数据库查出的字符串直接 echo 到 HTML 中,就可能触发 XSS。比如:<div><?php echo $_GET['q']; ?></div>,传入 q=<script>fetch('/api/steal?c='+document.cookie)</script> 就能偷 Cookie。
常见错误是只在入库前用 strip_tags() 或 filter_var() “清理输入”,但输出时没做任何处理——XSS 发生在浏览器渲染阶段,不是入库阶段。
-
htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8')是推荐写法:明确编码、兼容 HTML5、双引号和单引号都转义 - 别用
htmlentities()替代:它会把中文、emoji 等非 ASCII 字符也转成实体,导致显示乱码 - 不要依赖
magic_quotes_gpc(已废弃)或自定义正则“删 script 标签”——绕过方式太多,不可靠
输出到 JavaScript 字符串时不能只用 htmlspecialchars()
像 var name = "<?php echo $user_name; ?>"; 这种写法,即使 $user_name 经过 htmlspecialchars(),也可能被闭合引号后注入代码。例如输入 "(英文双引号)就能提前结束字符串,接着执行任意 JS。
正确做法是用 json_encode(),并确保外层引号匹配:
var name = <?php echo json_encode($user_name, JSON_UNESCAPED_UNICODE); ?>;
注意:json_encode() 输出的是带双引号的 JSON 字符串,所以变量声明里不要额外加引号;如果必须用单引号包裹,需手动替换或改用 addslashes() + 严格上下文校验(不推荐)。
富文本内容不能靠 htmlspecialchars() 解决
论坛、商品描述等需要保留 <p></p>、<img>、<strong></strong> 的场景,htmlspecialchars() 会把所有标签干掉,用户体验直接崩坏。
这时必须用白名单过滤器,而不是“删掉危险标签”的黑名单思路:
- 用
HTMLPurifier(需 Composer 安装),它按 W3C 规范解析 DOM,只放行你配置的标签和属性(如允许<img src>但禁止onerror) - 别手写正则匹配
<script></script>或on\w+=——HTML 结构太灵活,正则极易被绕过(比如大小写混写、空格变形、注释干扰) - 如果用 CKEditor、TinyMCE 等编辑器,务必开启服务端白名单校验,不能只信前端提交的内容
CSP 和 HttpOnly 是补充,不是替代
Content-Security-Policy 响应头能阻止内联脚本和未授权域名的 JS 加载,Set-Cookie: HttpOnly 能防止 JS 读取 Cookie——这两项对防御 XSS 很有用,但它们解决的是“攻击成功后的损害控制”,不是“阻止攻击发生”。
比如 CSP 没开 unsafe-inline,但页面里有 <script>alert(1)</script>,浏览器会拒绝执行;可如果攻击者把 payload 放进 <img src="x" onerror="..."> 且 CSP 允许内联事件,照样触发。
真正不可跳过的动作只有一个:每次把用户数据插入 HTML 之前,确认是否经过了上下文正确的编码。这个动作漏一次,整个防御体系就形同虚设。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











