多层防护为什么还漏?高防、CDN、WAF谁管什么

高防、CDN、WAF 都开着,攻击还是打穿了。本文用 10 个问答讲清多层防护的职责边界、规则冲突、灰流量归谁,以及怎么逐层验证防护真的在生效。

Q:多层防护到底分了哪几层,各层管什么?

  • 边缘层(CDN / Anycast):做粗粒度的活——静态缓存、按 IP 与地域的粗限流、四层连接代理。它对业务无感知,看不到登录、下单这些语义。
  • 四层清洗(高防 IP / 清洗中心):看 pps、bps、新建连接数、包长分布,靠特征和流量基线丢包。它不解析 HTTP,所以「每个请求都合法」的 CC 它管不了。
  • 七层 WAF:解析 URI、Header、Cookie、Body,负责 CC、注入、爬虫、越权这类语义级判定。它看不到被四层丢掉的包。
  • 源站(Nginx 与应用):最后一道,靠限流、连接数上限、缓存兜底,也是唯一知道业务逻辑的一层。
  • 一句话:每一层只能处理它看得见的东西,看不见的既拦不住,也放不过。

Q:既然都开了,为什么攻击还能漏过去?

  • 源站 IP 没藏住:攻击者直接打源站 IP,前面几层等于不存在。这是最常见的「防护失效」,跟产品能力无关。
  • 只代理了主域名:img.、static.、api. 这些子域还直连源站,攻击者专挑最软的那个打。
  • 阈值定得太高:四层清洗阈值按大促峰值设的,几百 QPS 的小流量 CC 一直触发不了。
  • 路径漏配:WAF 只挂了部分 location,/api/、WebSocket 走了 bypass 分支,规则从来没执行过。
  • 灰流量:攻击混在正常流量里,量不够触发清洗,特征不够触发规则,两边都觉得「不是我该管的」。

Q:四层清洗完,七层还要不要再查一遍?

  • 要查,但不能重复做同一件事。四层只能丢畸形包和超阈值的流量,剩下的仍然是合法 HTTP,必须由七层判语义。
  • 分工口诀:四层管「量」,七层管「意」,源站管「底」。
  • 重复防护的代价很实在:两层都按 IP 做限流,正常用户的「违规分」被扣两次,误封率直接翻倍。
  • 同理,两层都做连接数限制时,阈值必须一层比一层宽,否则内层永远先触发,外层的告警就成了噪音。
  • 判断有没有重复:把某一层的限流临时关掉,看另一层的拦截量有没有明显变化。没有变化,说明这层本来就没干活。

Q:多层规则打架会造成什么后果?

  • 白名单打架:CDN 侧放行了某 IP,源站 WAF 还在封它,用户永远打不开,你在 CDN 控制台查半天查不出问题。
  • 黑名单打架:边缘误封了搜索引擎 IP,七层日志里还在刷正常请求,看板上一切正常,收录却掉了。
  • 顺序打架:边缘限流返回 429,七层把它当成攻击信号再加一级封禁,用户被「双重判刑」,申诉时两边都不认。
  • 计数打架:两层各自按 IP 计数,每层阈值都留了 30% 余量,合起来正好超过业务上限。
  • 根治办法只有一个:白名单和封禁名单单一来源,一份文件下发到各层,别在三个控制台各维护一份。

Q:CDN 和 WAF 都开了,为什么 CC 还是打进来了?

  • 缓存没命中就等于没防护:攻击者给每个请求加随机参数 ?_=123456,cache key 全部不同,白白回源。
  • cache key 里带了 Cookie 或 UA:随手改一个头就击穿缓存,一个 URL 变成无限个缓存对象。
  • 只缓存静态资源:图片 CSS 被挡住了,动态接口直连源站,攻击者当然只打接口。
  • 只缓存 GET:POST 全部回源,登录、搜索、提交表单全在源站扛。
  • 判断方法:看 CDN 的 miss rate 和回源 QPS。回源 QPS 接近总 QPS,说明缓存形同虚设,先去修 cache key 再谈拦不拦得住。

Q:怎么验证每一层真的在拦?给套能直接跑的方法

  • 三段验证:直连源站打一次、走 CDN 打一次、带攻击特征再打一次。
  • 直连源站能正常打开,说明源站裸奔,前面几层白买。
  • 走 CDN 时响应头里应该有节点标识,一个都没有就是 DNS 没切过去或解析还没生效。
  • 三次结果完全一致才是异常(说明只有一层在干活);不一致才正常。
# ① 直连源站:用 --resolve 强制打到源站 IP,验证源站是否裸奔
curl -sk -o /dev/null -w "origin %{http_code} %{time_total}\n" \
  --resolve www.example.com:443:203.0.113.10 https://www.example.com/

# ② 走 CDN:看有没有落到边缘节点
curl -sI https://www.example.com/ | grep -Ei "server|via|x-cache|ali-swift|x-cache-lookup"

# ③ 带 CC 特征:同一 IP 短时间打同一动态 URL
for i in $(seq 1 50); do
  curl -s -o /dev/null -w "%{http_code} " "https://www.example.com/api/list?r=$i"
done; echo
  • 第 ③ 步如果全程 200,说明七层限流没生效;如果出现 429/403 就是拦住了,记下第几个请求被拦,那就是你的实际阈值。
  • 这套验证每周做一次,比看供应商报表有用得多。

Q:灰流量到底该由哪一层负责?

  • 灰流量是既不像教科书攻击、也不像干净用户的那部分:代理池、秒拨 IP、正常 UA 的慢速扫描。
  • 归属原则:谁有上下文,谁负责。只有七层知道「这个 IP 五分钟内翻了 800 个商品页」,行为判定归七层。
  • 四层只负责把「量」压下来,不负责判性质。让四层去猜行为,结果只能是把整段 IP 封掉。
  • 源站负责兜底:连接数、队列长度、缓存命中,这三样决定灰流量最终能不能把站压死。
  • 所以灰流量不是「选一层解决」,而是边缘限速 + 七层行为 + 源站兜底三段式,缺一段就会在那一层堆爆。

Q:各层日志对不上,怎么串起来查?

  • 口径不同是根因:边缘记的是节点看到的地址,四层记的是包,源站记的可能是回源 IP。
  • 不配 set_real_ip_from,源站日志里全是 CDN 节点地址,按 IP 统计出来的 Top 榜全是假的。
  • 加一个 request id,从边缘透传到源站并落日志,出问题按 id 一把串起来,比对着时间戳猜可靠。
# 边缘没给 request id 就本地生成,然后透传 + 落日志
map $http_x_request_id $rid {
    default $http_x_request_id;
    ""      $request_id;
}
add_header X-Request-ID $rid always;

log_format with_rid '$remote_addr - [$time_local] "$request" $status '
                    'rid=$rid rt=$request_time urt=$upstream_response_time';
  • 源站只信任回源段发来的 XFF,并且只取第一段,否则客户端自己伪造一个 XFF 就能把日志写花。
  • 串联之后你会发现很多「漏过去」的请求其实是被上一层放行的,问题立刻从「防护没生效」变成「规则写错了」。

Q:配置顺序和优先级怎么定?

  • 顺序固定为:白名单 → 静态缓存 → 粗限流 → 行为判定 → 源站限流。
  • 白名单最高,但白名单必须最小化;静态资源放行优先于限流,否则一张图片被限流,页面直接散架。
  • Nginx 的坑要记住:正则 location 会盖过前缀 location,在 if 里写 limit_req 根本不生效,必须写在 location 层。
# 白名单单一来源,一处维护,各层共用
include /etc/nginx/conf.d/allowlist.conf;

server {
    # ① 静态资源先走缓存,不参与限流
    location ~* \.(js|css|jpg|png|webp|woff2)$ {
        expires 7d;
        limit_req off;
        try_files $uri =404;
    }
    # ② 动态接口单独限流,429 而不是 503
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        limit_req_status 429;
        proxy_pass http://backend;
    }
    # ③ 回源只允许 CDN 段访问
    include /etc/nginx/conf.d/origin_only_cdn.conf;
}
  • 改完 nginx -t && nginx -s reload,不要 restart,restart 会断掉当前所有连接。
  • limit_req_status 429 必须显式写,默认的 503 会被 CDN 缓存成一张故障页,误伤所有访客。

Q:还有什么补充建议?

  • 每一层都要有独立开关,出事能单独关掉一层定位问题。做成「一处开全站开」的方案,排查时没有任何手段。
  • 层数不是越多越好。每多一层就多一个故障点和一份误封率,小站两层(CDN + 源站限流)已经够用。
  • 定期做一次「关掉某层看会不会被打穿」的验证,防护的有效性靠验证,不靠采购清单。
  • 把各层职责写成一页纸贴在运维文档里,出了事谁先看哪个日志一目了然。
  • 最后一句:多层防护的关键不是层数,而是层与层的边界。边界糊了,层数再多也只是重复劳动加重复误封。