攻击停了不等于攻击者走了,撤防顺序错了会把源站直接暴露。本文用 10 个问答讲清怎么判断攻击真的结束、撤防该按什么顺序做、临时规则和白名单怎么回收。
Q:攻击停了就能马上撤防吗?
- 不能。攻击结束和攻击者离开是两件事。
- CC 攻击成本极低,攻击者常做「打了停、停了打」的试探:先压十分钟,看你什么时候把防护撤掉,一旦确认你恢复了裸奔状态,第二波会打得更狠。
- 从处置记录看,回打高发在撤防后 2 到 6 小时。所以撤防是一个降档加观察的过程,不是一次开关。
- 判断依据必须是客观指标,不是「监控不响了」。告警是阈值触发的,攻击量降到阈值以下,不代表攻击停止。
Q:怎么判断攻击真的结束了?
- 四个信号同时满足,再考虑动手:
- 攻击特征量回落:按小时统计 403 与 429 的条数,连续 6 到 12 小时回落到近 30 天同时段基线的 1.5 倍以内。
- 源站资源回位:CPU、P95、回源 QPS 三项都在日常区间内波动,没有锯齿状尖峰。
- 攻击源退榜:Top IP 榜里原来整段 /24 刷的地址消失,剩下的请求呈长尾分布,接近正常用户。
- 业务指标回来:登录成功率、下单量、咨询量恢复到攻击前水平。这条最容易被忽略,但最能说明业务真的恢复了。
# 按小时看状态码分布,确认 403/429 是否真的回落
awk '{print substr($4,2,14), $9}' /var/log/nginx/access.log \
| sort | uniq -c \
| awk '{printf "%s %s -> %s\n", $2, $3, $1}' | tail -48
Q:撤防该按什么顺序做?
- 一条总原则:先撤最贴业务的,最后才动最外层的。顺序反了,等于把源站 IP 直接摊给攻击者看。
- 第一步:撤挑战页与硬封禁。人机验证降回观察模式,封禁从永久改成 1 小时自动解封。
- 第二步:限流阈值逐步上调,从战时档位回到日常档位,每档之间间隔 24 小时。
- 第三步:关掉战时专用的严格规则组,例如 UA 黑名单、可疑参数拦截。
- 第四步:最后才考虑撤高防独享带宽或 CDN 兜底。这一步涉及 DNS 与 BGP,切换慢、可逆性最差,必须放在最后。
- 每一步都要留回切按钮,也就是上一版配置的快照。
Q:降档具体怎么配,两套阈值怎么写?
- 核心思路:两套 zone 同时存在,只切换调用哪一套。不要运行时改 rate,
limit_req_zone的速率是定义时固定的。
# http 块:战时与日常两套 zone 并存,互不影响
limit_req_zone $binary_remote_addr zone=cc_war:20m rate=5r/s;
limit_req_zone $binary_remote_addr zone=cc_norm:20m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=cconn:10m;
# /etc/nginx/conf/levels/war.conf —— 战时档
limit_req zone=cc_war burst=10 nodelay;
limit_conn cconn 10;
limit_req_status 429;
limit_conn_status 429;
# /etc/nginx/conf/levels/normal.conf —— 日常档
limit_req zone=cc_norm burst=40 nodelay;
limit_conn cconn 40;
limit_req_status 429;
limit_conn_status 429;
# server 或 location 内只引用一份,切换就等于换文件
include /etc/nginx/conf/cc_level.conf;
- 注意两点:
limit_req_status 429必须显式写,默认 503 会被 CDN 或浏览器缓存成故障页;20m 的 zone 大约能装十几万个 IPv4 键,够中小站用。 - 降档不是把限流关掉,而是把阈值放宽。日常档也必须有限流,这是底线。
Q:临时白名单为什么是最大的坑?
- 攻击期间加的白名单,本质是临时通行证,但绝大多数站点从来没删过。三个月后你在日志里看到某个 IP 无限刷,查半天发现是当时自己加的。
- 解决办法只有一条:白名单必须带到期时间,并且由脚本自动清理,不靠人记。
- 文件名即到期日,一眼能看出哪一批过期了。
# /etc/nginx/conf/wl/20261103.conf
# expire=2026-11-03 reason=攻击期间客户后台联调
allow 203.0.113.24;
allow 198.51.100.0/28;
# 到期清理脚本 /usr/local/bin/wl_gc.sh
#!/bin/bash
cd /etc/nginx/conf/wl || exit 1
today=$(date +%Y%m%d)
for f in *.conf; do
d="${f%.conf}"
if [ "$d" -lt "$today" ]; then
mkdir -p /etc/nginx/conf/wl_expired
mv "$f" /etc/nginx/conf/wl_expired/ && echo "expired: $f"
fi
done
nginx -t && nginx -s reload
# 每天凌晨四点跑一次,过期白名单自动下线
0 4 * * * /usr/local/bin/wl_gc.sh >> /var/log/wl_gc.log 2>&1
- 如果白名单是通过限流键放行的,用
geo加map把键置空即可,空键不计数,比if更稳,也和上面同一套文件管理。
Q:观察期设多久,盯哪几个指标?
- 节奏:降一档观察 24 小时,再降一档观察 48 小时,共 72 小时后回到日常档位。
- 四个指标:429 与 403 占比、回源 QPS、P95 延迟、用户投诉量。
- 判断线可以这样定:拦截占比正常应低于 0.5%;突然升到 5% 以上,基本就是又被打了一轮。
- 回到日常档后仍保留水位告警:任意 5 分钟内 429 条数超过阈值就通知,比人盯日志可靠得多。
Q:撤完又被打了怎么办?
- 这是常态,预案要备两样东西:配置快照和一键回切。
- 快照:每次改动前把整个配置目录打包,文件名带时间戳,保留 30 天。
- 回切脚本:把战时档文件拷回去,
nginx -t通过再 reload,全程控制在 30 秒内。
#!/bin/bash
# /usr/local/bin/cc_mode.sh war|normal
set -e
SRC="/etc/nginx/conf/levels/$1.conf"
DST="/etc/nginx/conf/cc_level.conf"
[ -f "$SRC" ] || { echo "no such level: $1"; exit 1; }
cp -a /etc/nginx "/root/nginx.bak.$(date +%Y%m%d%H%M%S)"
cp "$SRC" "$DST"
nginx -t && nginx -s reload && echo "switched to level: $1"
- 另外一件事:战时就把 DNS TTL 降到 60 秒。TTL 还挂着 3600 秒,回切一个高防节点要等一小时,这是「预案写了但没用」最常见的原因。
Q:撤防最容易犯的几个错?
- 一次性全撤:省事,然后被第二波打穿。
- 把「监控不响」当结束:告警阈值之下仍有攻击,只是没触发。
- 白名单不回收:等于给攻击者留后门,而且事后很难查。
- 没有快照:要回切时才发现没人记得战时参数。
- TTL 没提前降:切换窗口太长,等于没有预案。
- 不通知业务方:降档后误伤或放开的动作没同步,客服口径对不上,投诉会翻倍。
Q:撤防之后要留哪些材料?
- 攻击时间段、峰值 QPS 与带宽、拦截条数、封禁的 IP 段清单。
- 误封工单与处理结果。这是下一次调阈值的直接依据,比任何经验判断都准。
- 账单:高防独享、流量超出、短信验证码被刷的费用。年底编安全预算时,这些数字最有说服力。
- 最终稳定的规则文件与注释,写清哪些是战时加的、为什么现在可以撤。
Q:还有什么补充建议?
- 战时包平时就准备好:三个档位文件、切换脚本、快照目录。每月空跑一次切换,别等打起来才第一次用。
- 撤防不是把防护关掉,而是把严格换成常态。日常档松一点,但不能没有。
- 给撤防设负责人和时间点。攻击一结束没人推动,防护就会长期挂在战时配置,误封率慢慢累积,最后变成新的投诉源。
- 最后一句:能不能安全撤防,考验的不是防护能力,是你对自身流量基线有多熟。