低频CC攻击指的是每个 IP 每分钟只发几次请求、靠几千个 IP 同时打的那种攻击。本文讲清它为什么躲得过常规限频、日志上怎么认出来、四层限速怎么配,以及误伤怎么控。
Q:什么叫低频CC攻击?和常见 CC 有什么不一样?
- 常见的 CC 是单 IP 高频:一台肉鸡几十上百 r/s 硬刷一个动态接口,特征明显,一个
limit_req或 WAF 频率规则就能截住。 - 低频 CC 反过来:单个 IP 每分钟只请求 2 到 10 次,任何常规阈值都不会触发,但 IP 数量上千到上万,聚合 QPS 照样能到几百甚至几千。
- 成本结构被反转了。攻击者租代理池按量付费,每个 IP 只打几下,成本极低;防守方却要为每个请求做计算、查库、拼页面,付出的是真实 CPU。
- 它和慢速攻击(Slowloris)不是一回事。慢速攻击是协议层故意不结束连接,耗尽连接表;低频 CC 是正常的应用层请求,只是发得稀疏,包怎么看你都是个正常访客。
Q:为什么按 IP 的 limit_req 对它完全没用?
- 阈值悖论。设成 10r/s,拦不住 3r/min;设成 1r/min,正常用户打开一个页面就会并发拉几十个资源,先被你自己拦掉。
- 限流是分桶的。key 是
$binary_remote_addr,一万个 IP 就是一万个桶,每个桶都装不满,规则永远不触发。 - 所以低频 CC 的对手不是「单 IP 速率」,而是「全站总量」和「请求分布形态」。继续在单 IP 阈值上加码,方向就错了。
Q:怎么从日志里认出它是低频 CC,而不是流量真涨了?
- 看三个数:全网 QPS 涨 2 到 5 倍;单 IP 请求数的 99 分位很低(一分钟不到 10 次);被请求的 URL 高度集中在少数几个动态路径。
- 真实流量涨是 UV 和 PV 同比例涨、来源 Referer 分散、转化率平稳;低频 CC 是 PV 涨、会话数不涨、跳出率接近 100%、Referer 基本为空。
- 源站侧还有一个很好用的信号:CPU 占用不算高,但 MySQL 连接数打满、PHP-FPM 队列堆积、TTFB 从几十毫秒涨到一秒以上。
# 每分钟每 IP 的请求数分布(取最后一分钟的前 20 名)
awk '{print substr($4,2,17), $1}' access.log \
| sort | uniq -c | sort -rn | head -20
# 单 IP 请求数量的分位数:p50 很低、p99 也很低,就是典型低频分布式
awk '{c[$1]++} END{for(i in c) print c[i]}' access.log \
| sort -n | awk '{a[NR]=$1} END{print "p50="a[int(NR*0.5)], "p90="a[int(NR*0.9)], "p99="a[int(NR*0.99)], "max="a[NR]}'
# 看攻击集中在哪些 URL(去掉静态资源)
awk '{print $7}' access.log | grep -vE '\.(css|js|png|jpg|ico|woff2)$' \
| sort | uniq -c | sort -rn | head -20
- 如果 p99 只有个位数、max 也不高,但总请求量翻了几倍,基本可以定性为低频分布式 CC,不要再往单 IP 阈值上找答案。
Q:第一道闸,全站总量限速怎么配?
- 思路是给整站一个与 IP 无关的总桶:key 用常量或域名,所有请求共用同一个漏桶。IP 再多也绕不过总量。
- 这一步的正确位置是「熔断」,不是主防线。它保护的是源站不被彻底打死,代价是高峰时会伤到正常用户,所以只在动态接口上生效。
# http 段:三个桶,key 分别是「整站」「域名」「客户端 IP」
limit_req_zone $server_name zone=site_total:10m rate=300r/s;
limit_req_zone $server_name zone=api_total:10m rate=60r/s;
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;
map $request_uri $is_api {
default 0;
~^/api/ 1;
~^/(search|list|product)/ 1;
}
server {
# 静态资源不进总闸,否则会把正常用户一起熔断
location ~* \.(css|js|png|jpg|jpeg|gif|ico|woff2?)$ {
limit_req zone=per_ip burst=60 nodelay;
expires 7d;
}
location / {
limit_req zone=site_total burst=600 nodelay;
limit_req_status 429;
limit_conn_status 429;
}
location /api/ {
limit_req zone=api_total burst=120 nodelay;
limit_req zone=per_ip burst=20 nodelay;
limit_req_status 429;
}
}
- rate 要按你实测的正常峰值往上留 30%,别拍脑袋。burst 给 2 倍 rate 的一秒量,nodelay 让超出部分立刻拒绝而不是排队(排队在攻击下只会把连接堆死)。
- 状态码用 429 而不是 444。444 直接断连,用户端什么都看不到,监控和埋点也拿不到数据,事后没法算误杀。
Q:按 URL 和接口维度限速,为什么比按 IP 更有效?
- 低频 CC 的请求一定集中在「贵且不可缓存」的路径上。全站限速是粗的,按接口限速才打得到痛点。
- 一个搜索接口单次耗时 300ms,30 QPS 就能占满 10 个 PHP 进程。这时候全站 QPS 看着还很正常,但站点已经挂了。
- 带随机参数的接口天然绕过缓存,是最容易被选中的靶子,必须单独限。
# 贵接口单独设桶,key 用「接口 + IP」,允许同一 IP 稍高频但总量受限
limit_req_zone $binary_remote_addr$uri zone=expensive:10m rate=2r/s;
location ~ ^/(search|ajax_list|detail)$ {
limit_req zone=expensive burst=5 nodelay;
limit_req_status 429;
# 兜底:短连接,防止慢请求占住 worker
keepalive_timeout 5s;
}
- 桶越细,攻击者要准备的 IP 越多。「整站 1 个桶」变成「10 个贵接口各 1 个桶」,同样攻击量就要 10 倍 IP 才能打穿。
Q:指纹和会话维度怎么做?通行证 cookie 要注意什么?
- 低频 CC 的真正难点是「没有稳定标识」。IP 随时换,UA 随便改,唯一能相对稳定绑定的只有设备侧信息。
- 做法是首次访问下发一个通行证 cookie:值由服务端 HMAC 签名,绑定 UA 摘要和 /24 网段,有效期 20 到 30 分钟。
- 之后所有请求按 cookie 分桶。攻击者要绕过,就得为每个 IP 维持一套真实浏览器环境,成本立刻从「换 IP」变成「养设备」。
- 没有 cookie 的请求走更严格的桶——正常浏览器访问一次页面后一定会带 cookie,缺失本身就是信号。
map $cookie_pass $cc_key {
default $binary_remote_addr; # 无通行证:按 IP 严格限
"~^[A-Za-z0-9._-]{20,}$" $cookie_pass; # 有通行证:按设备限
}
limit_req_zone $cc_key zone=with_pass:10m rate=5r/s;
# 下发通行证(应用层签发,这里只是示例形态)
add_header Set-Cookie "pass=$request_id.$time_iso8601; Path=/; Max-Age=1800; HttpOnly; SameSite=Lax" always;
- 三个硬要求:签名必须带服务端密钥(不能用时间戳裸拼)、salt 定期轮换、绝不覆盖静态资源路径——否则首屏加载就会被打断。
Q:验证码什么时候该下发?什么时候反而拖垮自己?
- 低频 CC 有个反直觉的点:每个 IP 只来三次,你给它发验证码,等于你替它承担了三次挑战的渲染与校验成本。
- 所以验证码只对「已触发总闸 + 无通行证 + 命中高危路径」这三个条件同时成立的请求下发,别全站挂。
- 优先用无感方案(JS 计算、指纹打分),有交互的滑块、点选放在最后。图形字符码 OCR 识别率已超 90%,基本可以放弃了。
- APP 和小程序塞不了 JS 挑战,只能走签名 + 设备指纹 + 按设备维度限频,别按 IP,移动网络下面几百个真人共用一个出口。
Q:CDN 和高防侧要开哪些能力才拦得住?
- 边缘 QPS 基线告警,按 URL 分组,不按 IP。低频 CC 在 IP 维度上永远是「正常」的,只有 URL 维度才看得出异常。
- 人机挑战、Bot 管理、按 URL 限速、突发模式(俗称 5 秒盾)这四样,前两个对付脚本,后两个对付量。回源量降一半,等于免费多出一半防护能力。
- 一定要拿到带
request_id的边缘日志。没有它,源站侧的告警和边缘侧的拦截记录对不上,误杀根本算不清。
Q:误伤怎么控?哪些流量必须优先放行?
- 必须白名单:搜索引擎蜘蛛、支付回调、合作方 API(固定出口 IP 或 HMAC 签名)、已登录用户的结算路径。
- 白名单不是「无桶」,是「单独一个宽松桶」。完全不限速的白名单,迟早会变成被打穿的入口。
- 灰度节奏:先只记录不拦截,跑 3 到 7 天看拦截比;再对未登录用户放 5%;最后才全量。
- 两条红线:正常用户的 429 比例低于 1%,登录与下单路径的误拦必须是 0。超了立刻降级,别硬扛。
Q:还有什么补充建议?
- 低频 CC 本质是一场成本战。你每加一层规则,攻击者的成本没涨多少,你的运维成本涨了不少。优先级应该是:缓存 → 贵接口限流 → 总闸熔断 → 指纹 → 验证码,别一上来就全套。
- 规则上线前自己压一遍。用
ab或wrk打贵接口,看 429 比例和源站恢复时间,比事后拍脑袋调参靠谱。 - 日志至少留 30 天,带 IP、UA、URI、状态码、耗时的原始访问日志,事后复盘和举证都靠它。
- 攻击结束后把临时规则回收掉。临时总闸忘了关,下次大促自己把自己限死,这种情况比攻击本身更常见。
- 所有规则都写到期复核日。没有复核日的限流规则,半年后一定是一条谁也说不清来历的历史包袱。