referer校验不靠谱,仅作辅助;限流必须用redis;filter_var不能防注入;识别扫描器应看请求特征而非ip。

用 $_SERVER['HTTP_REFERER'] 做 Referer 校验靠谱吗?
不靠谱,仅作辅助。Referer 完全由客户端控制,可被任意伪造或清空(比如浏览器禁用、cURL 不带、HTTPS 页面跳转到 HTTP 时自动丢失)。真实业务中见过大量攻击脚本直接设置 Referer: https://yourdomain.com 绕过校验。
如果非要加,只建议用于非关键场景(如静态资源防盗链),且必须配合其他手段:
- 校验前先检查
$_SERVER['HTTP_REFERER']是否非空,再用parse_url()提取 host,避免 parse 错误 - 用白名单匹配 host,不用
strpos()模糊包含(防止evil.com/yourdomain.com这类绕过) - 绝不单独依赖它做登录、支付、删除等敏感操作的权限判断
限流该用 Redis 还是 PHP-FPM 内存?
必须用 Redis(或其它独立存储)。PHP-FPM 进程间内存不共享,apcu_inc() 或 file_put_contents() 在多进程下会严重失准,实测 100 并发请求可能计数偏差 ±30% 以上。
推荐用 Redis 的原子操作实现滑动窗口或令牌桶:
立即学习“PHP免费学习笔记(深入)”;
- 滑动窗口:用
ZSET存时间戳,ZCOUNT+ZREMRANGEBYSCORE组合清理过期项 - 令牌桶:用
INCR+EXPIRE控制桶生命周期,注意INCR返回值要立即判断是否超限 - 务必设置 key 过期时间(如
60秒),否则 Redis 内存持续增长
示例片段(滑动窗口):
$key = 'rate:'.$ip.':'.date('YmdHi'); // 每分钟一个桶
$now = time();
$window = 60;
Redis::zAdd($key, $now, $now);
Redis::zRemRangeByScore($key, 0, $now - $window);
$count = Redis::zCard($key); // 实际请求数
为什么 filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) 不能防注入?
它只校验邮箱格式是否合法,不处理特殊字符、编码绕过或上下文逃逸。攻击者完全可以用 admin%40example.com(URL 编码)、test@example.com%00(Null 字节截断)、甚至 admin@example.com" onclick="alert(1)(输出到 HTML 未转义时直接 XSS)。
防御要点在“上下文”:
- 入库前:用 PDO 预处理(
bindValue())或mysqli_real_escape_string()(仅限 MySQL 且已弃用) - 输出到 HTML:用
htmlspecialchars($str, ENT_QUOTES, 'UTF-8') - 拼接 Shell 命令:绝对禁用
exec()等函数,改用白名单参数封装 - 邮箱本身无需过滤内容,但存储后若用于发信,需额外验证 MX 记录防垃圾注册
如何快速识别并拦截扫描器行为?
看请求特征比看 IP 更有效。真实用户极少在 1 秒内连续请求多个不存在路径(如 /phpmyadmin/、/wp-config.php、/admin/login.php),也几乎不会用非常规 User-Agent(如 sqlmap/1.7、nikto/2.1)。
可在入口统一中间件里做轻量级检测:
- 统计 5 分钟内同一 IP 的 404 数量,超过 10 次且路径含常见扫描关键词(
phpmyadmin、backup、config),临时封禁 1 小时(写入 Redis) - 对 User-Agent 包含
sqlmap、nmap、dirbuster的请求直接返回 403 - 记录异常请求日志(IP、UA、URI、时间),但别记录 POST body(隐私与性能风险)
注意:不要用 sleep() 或长耗时操作做“反爬”,高并发下反而拖垮服务。
最常被忽略的是「信任边界」——把 Nginx 的 $remote_addr 当成真实 IP 直接用,却没处理代理转发(X-Forwarded-For 可伪造)。真正要限流或封禁,得结合 $_SERVER['REMOTE_ADDR'] 和可信代理列表做层层校验。











