做 CC 攻击防护久了会发现,真正让人头疼的不是拦不住攻击,而是拦错了人:正常用户被限速、被反复弹验证码、甚至整个公司出口 IP 被封。本文用 9 个问答讲清 CC 攻击误封的成因、分级放行怎么做、白名单怎么配、上线怎么灰度,配置与命令可直接复制。
Q:CC攻击防护为什么总会误封正常用户?
根因只有一个:限速的计数维度是 IP,但 IP 不等于用户。三种场景必然踩雷:
- 企业或学校 NAT 出口:一家 200 人的公司对外只有一个 IP,员工同时刷新页面,该 IP 的请求数天然是普通访客的几十倍。
- 移动网络共享池:运营商 CGNAT 后面可能挂几千个用户,同一个公网 IP 的请求量本来就高。
- 家庭宽带动态 IP:上午是攻击者的 IP,下午分给了正常用户,黑名单没及时过期就会误伤。
更隐蔽的问题是阈值设定方式:很多配置直接按全站平均 QPS 的倍数来设,忽略了头部 IP 天然就高。按 rate=5r/s 一刀切,NAT 出口必然被切掉。
Q:怎么判断误封已经发生了?看哪些指标?
四个信号同时出现两个,就要怀疑误封:
- 限速触发次数明显上升,但带宽和 CPU 没有同步上涨。
- 人机验证的通过率异常低,或者验证弹窗量突然翻倍。
- 客服与在线咨询里「网站打不开」「一直转圈」集中出现。
- 被封 IP 归属集中在住宅与移动段,而不是 IDC 机房段。
# 1. 统计今天被限速拒绝的请求来源(按 IP 聚合 Top20)
grep 'limiting requests' /var/log/nginx/error.log \
| awk '{print $NF}' | sort | uniq -c | sort -rn | head -20
# 2. 抽查这些 IP 的归属:是机房还是住宅/移动
for ip in $(grep 'limiting requests' /var/log/nginx/error.log \
| awk '{print $NF}' | sort -u | head -20); do
echo "== $ip"; whois "$ip" | grep -Ei 'netname|descr|country' | head -3
done
判读口诀:触发限速的 IP 集中在 IDC 机房段,是攻击;散落在三大运营商的住宅与移动段,大概率是误封。这个判断比看请求数曲线更直接,因为它直接回答了「被拦的是谁」。
Q:什么是分级放行?为什么不能只有黑名单?
只有「拦」和「不拦」两档,等于把安全策略做成开关,误伤一定发生。正确做法是分三级:
- 黑名单:明确恶意来源,直接拦截,带超时自动解封。
- 灰名单:行为可疑但不确定,只降速或下挑战,不封禁。这一档是误封的缓冲带。
- 白名单:可信对象,免拦截但保留温和限速。注意白名单不等于完全不管,蜘蛛和回源段照样可以有频率上限。
关键理念:第一档处置永远是降速,最后一档才是封禁。降速对正常用户的感知是「有点慢」,封禁的感知是「网站挂了」,代价差一个量级。
Q:哪些对象应该进白名单?geo 怎么配?
常见六类:CDN 或高防的回源 IP 段、搜索引擎官方蜘蛛段、支付与短信等第三方回调、公司办公出口与监控探针、已登录用户、长期老客户的固定出口。
# 用 geo 建白名单,比在 map 里堆正则好维护,也方便按文件更新
geo $is_trusted {
default 0;
10.0.0.0/8 1; # 内网
192.168.0.0/16 1;
include /etc/nginx/conf.d/trusted_cdn_ips.conf; # CDN 回源段
include /etc/nginx/conf.d/trusted_se_ips.conf; # 搜索引擎蜘蛛段
}
# 空 key 在 limit_req 里等于「不计入限速」,这是只限可疑对象的通用写法
map $is_trusted $limit_key {
0 $binary_remote_addr;
1 "";
}
limit_req_zone $limit_key zone=perip:10m rate=10r/s;
server {
location / {
limit_req zone=perip burst=30 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}
}
白名单文件要能热更新:改完 trusted_cdn_ips.conf 执行 nginx -t 加 reload 即可,不需要改主配置。建议给每个网段加注释标明来源和加入日期,半年审计一次,清理掉已经不用的段。
Q:NAT环境下按IP限速必然误封,有别的计数维度吗?
有,把计数键从 IP 换成会话标识。做法是首次访问时给访客下发一个匿名 Cookie,后续限速按 Cookie 计数:
map $cookie_uid $limit_key2 {
"" $binary_remote_addr; # 没有 Cookie 的(基本是脚本)按 IP 计
default $cookie_uid; # 有 Cookie 的按会话计
}
limit_req_zone $limit_key2 zone=persession:10m rate=8r/s;
这招有个天然优势:真实浏览器会带 Cookie,纯脚本爬虫不会,等于顺手完成了一次客户端区分。缺点是攻击者也能脚本化地带 Cookie,所以它解决的是误封问题,不是攻击问题,两层要叠加使用。
并发限制同理。limit_conn 按 IP 计数在 NAT 下更危险:一个公司 200 人共用出口,现代浏览器单域名并发 6 个,理论上能到 1200 并发。并发阈值必须按 NAT 规模留出量级余量,不能照抄单机建议值。
Q:白名单会被攻击者利用吗?怎么防止被绕过?
会,三类风险要提前堵:
- 源 IP 伪造:TCP 握手无法伪造源 IP,但 UDP 可以。反射攻击伪造的源 IP 若恰好命中白名单,等于替攻击者开了门,所以白名单放行 UDP 服务要格外谨慎。
- 蜘蛛段被滥用:IP 确实属于搜索引擎 ASN,但操作者在做恶意抓取。IP 验证只能确认「身份」,不能确认「行为」。
- 内网横向:白名单里的内网段一旦有机器被拿下,就成了绕过入口。
对策一:白名单只放行必要路径,不做全站放行。
# 支付回调只允许指定来源段,路径级精确放行
location /pay/notify/ {
allow 203.0.113.0/24;
allow 198.51.100.7;
deny all;
}
# 后台只允许办公出口
location /admin/ {
allow 10.20.0.0/16;
deny all;
}
对策二:白名单免拦截但不忘限速。给蜘蛛单独一个温和上限(如 2r/s、burst 10),既不影响收录,也能压住「真 IP 干坏事」的规模。
Q:策略上线怎么灰度?直接开拦截太冒险了
三段灰度,每段观察一个完整业务周期(必须包含周末,工作日和周末的流量分布完全不同):
# 第一阶段:只记录不拦截。把日志级别调低,便于 grep 统计触发量
limit_req_log_level warn;
# 第二阶段:只对动态接口生效,静态资源完全不限
location ~ ^/(api|search|login|cart)/ {
limit_req zone=perip burst=30 nodelay;
limit_req_log_level warn;
}
# 第三阶段:确认观察期内误封为 0,再扩到全站
location / {
limit_req zone=perip burst=30 nodelay;
limit_req_status 429;
}
第一阶段的验收标准是:把 warn 日志里被标记的 IP 全部人工过一遍,确认没有住宅与移动段的高频正常用户,才进入下一阶段。这一步花半天,能省掉后面一周的客诉。
Q:已经被封的用户怎么自助解封?
不要只留一个邮箱。三个通道按性价比排序:
- 人机验证放行:最有效。收到 429 时返回一个验证页,通过即写入临时放行名单,正常用户秒过、脚本过不了。
- 带 Retry-After 的 429 页面:告知恢复时间,避免用户误以为网站挂了而离开。
- 客服工单:给客服一个查询 IP 是否被封的内部工具,避免靠猜。
# 封禁一律带超时,误封会自然消解,不要用永久封禁
ipset create ccban hash:ip timeout 3600 -exist
ipset add ccban 203.0.113.9 timeout 3600 -exist
# 给被封 IP 加一次会话刷新(用户通过验证后延长放行时间)
ipset add ccban 203.0.113.9 timeout 7200 -exist
# 观察当前封禁清单,找误封
ipset list ccban | head -30
返回体的处理细节很关键:429 要用自定义错误页,页面上写清「访问过于频繁,验证后可继续」并把 Retry-After 设为实际恢复秒数。只要不告诉用户原因,用户默认理解成网站故障,损失比拦截本身还大。
Q:还有什么补充建议?
- 白名单要台账化管理:每条记录包含来源、用途、加入日期、责任人、复核日期。没有台账的白名单半年后没人敢动。
- 把「限速触发率」纳入常规监控:触发率突然翻倍,往往不是攻击变强了,而是策略误伤了新流量。
- 安全策略必须可一键关停:保留一个开关(如改一个变量即可把限速 zone 换成放行 zone),出事时先恢复业务再复盘。
- 优先优化业务成本:被打的接口如果能上缓存,攻击的性价比立刻掉一个量级,比调限速阈值有效得多。
总结一句:CC 防护的成熟度不看拦掉了多少攻击,而看拦错的比例有多低。分级放行加灰度上线,是让这两件事同时成立的最短路径。