应读取时不处理$_GET,仅在输出到HTML时用htmlspecialchars()转义;输出到JS用json_encode(),输出到HTML属性需额外注意上下文安全。

直接用 htmlspecialchars() 处理输出,不是过滤输入
PHP 的 $_GET 本身不“危险”,危险的是你把它原样塞进 HTML 页面里。很多开发者误以为要先对 $_GET['name'] 做“清洗”再存或使用,结果反而破坏数据(比如把用户昵称 O'Reilly 变成 O'Reilly 并入库)。正确做法是:**读取时不处理,输出到 HTML 时才转义**。
常见错误现象:
• 页面显示 被执行
• 用户提交的单引号、尖括号在页面上变成乱码或实体编码
• 搜索关键词高亮后样式错乱
- 只在 输出到 HTML 上下文 时调用
htmlspecialchars($str, ENT_QUOTES, 'UTF-8') - 如果输出到 JavaScript 字符串(如
var name = "";),htmlspecialchars()不够 —— 改用json_encode($str, JSON_UNESCAPED_UNICODE) - 如果输出到 HTML 属性值(如
),确保加引号且用ENT_QUOTES - 别用
strip_tags()或正则删标签来“防 XSS”——它绕过方式太多,且会吃掉合法内容
不要用 filter_input() 自动转义输出
filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT) 这类函数看似省事,实则埋雷:它改写原始值,且语义模糊。“过滤”不等于“安全输出”,更不等于“验证”。你本想取一个带连字符的订单号 ORD-2024-abc,用 FILTER_SANITIZE_STRING(已废弃)或 FILTER_SANITIZE_FULL_SPECIAL_CHARS 会把它砍成 ORD2024abc,业务逻辑直接崩。
使用场景:
• 需要数字 ID?用 filter_var($_GET['id'], FILTER_VALIDATE_INT) 判断是否合法,不合法就拒收
• 需要邮箱?用 FILTER_VALIDATE_EMAIL
• 需要 URL?用 FILTER_VALIDATE_URL,但注意它不校验协议白名单
立即学习“PHP免费学习笔记(深入)”;
-
FILTER_SANITIZE_*系列是“清理”,不是“防护”,多数已被标记为 deprecated(PHP 8.1+) -
filter_input()的第三个参数只能做单次类型校验或简单清理,无法替代上下文感知的输出转义 - 若必须预处理(如日志记录),优先用
mb_substr()或白名单正则截断,而非无差别转义
URL 参数含 HTML 实体或 JSON 时特别容易翻车
当用户手动构造 URL,比如 ?q=,浏览器解码后 $_GET['q'] 是原始字符串 —— 此时如果你用 htmlspecialchars() 输出,能挡住;但若你把它拼进 innerHTML、或写进 href="javascript:..."、或当成 JSON key 直接 echo,就彻底失效。
性能影响很小,但兼容性陷阱多:
• htmlspecialchars() 默认不处理 /,但在 场景下,攻击者可闭合引号写入恶意 JS
• UTF-8 编码异常(如双字节截断)可能让转义失效,务必显式传 'UTF-8' 第三个参数
- 输出到
内部?用json_encode()包一层,别拼字符串 - 输出到
href或src?先用filter_var($url, FILTER_VALIDATE_URL)校验,再用htmlspecialchars()转义 - 参数本身就是 JSON 片段(如
?config={"theme":"dark"})?先json_decode(),失败就拒收,别直接echo
最常被忽略的一点:HTTP header 和重定向里的 $_GET
很多人记得页面 HTML 里防 XSS,却在 header('Location: /search?q=' . $_GET['q']) 或 header('X-User: ' . $_GET['name']) 里直接拼接,导致 CRLF 注入或响应头走私。这类漏洞不触发前端脚本,但危害不小——可伪造响应、劫持跳转、污染日志。
真实报错现象:
• 302 跳转后地址变成两行,第二行是 Set-Cookie: admin=1
• Nginx 日志里出现意外的换行和额外 header
- 重定向 URL 中的参数必须用
urlencode(),而不是htmlspecialchars() - 写自定义 header 时,值里禁止出现
\r、\n、:、(空格),可用str_replace(["\r", "\n"], '', $val)粗筛,或用白名单正则 - PHP 8.1+ 可用
header_remove()清除危险 header,但预防比清除重要
真正的难点不在函数怎么写,而在每次把 $_GET 拿出来用之前,都得问一句:它接下来去哪?HTML?JS?URL?Header?不同出口,防护手段完全不同。漏掉一个出口,整个防护就形同虚设。











