凌晨两点十七分,手机在床头柜上跳了起来。工作群里有人 @ 全员:“网站打不开了,老板早上八点要看。”你从被窝里坐起来,飞快回忆了一遍今天改过的代码——嗯,应该不是我的锅。然后开电脑、连 VPN、登服务器,一气呵成,熟练得让人心疼。
“网站打不开”这五个字,含义能差出十万八千里:有的是流量涨了,有的是服务倒了,也有一种是有人在拿你的服务器当健身器材。今天聊的就是最后这种:怀疑是 CC 攻击时,怎么先把火压下去。
一、先分清:到底是哪一种“打不开”
出了故障最忌讳一上来就抄起灭火器乱喷,先花三分钟做个体检、分个类,顺手也把该甩的锅甩明白。
正常的流量高峰:曲线是慢慢爬上来的,像早高峰的地铁,挤是挤,但门还能开。用户是“慢”不是“打不开”,CPU、带宽、连接数都在涨,可涨得有节奏、有天花板,过了那个点自己就回落。
服务器或程序自己崩了:这种很干脆,全站不分页面统统 502、504 或连接被拒,连那张万年不变的静态 Logo 都刷不出来。重启多半能恢复,日志里也看不到可疑来源——它纯粹是自己把自己绊倒了。
CC 攻击:特点三个字——猛、散、准。请求量某一分钟断崖式暴涨;来源 IP 铺得很开,像撒出去的一把沙子;打的目标又特别集中,往往是搜索、登录、商品详情这类“一请求就要查数据库”的动态接口。常见现象是首页一直转圈圈,图片却还开得出来,而带宽或连接数已经先被打满。
一句话判断:曲线是不是断崖、是不是只有动态页挂、连接数和来源是不是离谱,三条里对上两条,基本就可以往攻击方向想了。
二、第一步:临时止血,先让它喘口气
火先灭,再谈追凶。止血讲究快、准、可回退,别一锤子下去把自己也锁在门外。
1. 限制连接数。 让每个 IP 别占那么多坑,以常见的 Nginx 为例:
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_conn conn_per_ip 20;
限得太狠会误伤正常用户,太松又等于没限,按平时峰值留一倍余量比较稳。
2. 封高频 IP。 先把打头阵的揪出来:
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
结果丢进 ipset 批量封,比一条条手写 iptables 体面得多。但要清醒:封 IP 只是止血,不是治疗,人家换个 IP 池,这堵墙就跟你家小区门禁一样形同虚设。
3. 切静态页降级。 手里有现成的活动页、公告页静态版本,就先切过去;再果断砍掉最耗资源的功能——搜索、评论、下单查询,能关先关,能加验证码先加。用户看到“稍后恢复”总比看到 502 顺眼,数据库也能少挨几万刀。
三、第二步:定位问题,得知道谁在打你
止血撑不了几个小时,搞不清对方路数,封完一批又来一批。这时候日志就是你的案发现场。
# 哪个 URL 被打得最惨
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20
# 按分钟看请求量,判断是脉冲还是持续
awk '{print $4}' access.log | cut -d: -f1-2 | sort | uniq -c | tail -30
# UA 是不是复制粘贴出来的
awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head -10
对着结果找特征:
- 路径高度集中:几千个 IP 齐刷刷只刷同一个接口,正常用户的分布绝不会这么整齐。
- UA 雷同、缺失或一眼假:真人浏览器再懒也会报个像样的 UA,脚本有时候懒到连装都懒得装。
- 来源分散但网段集中:IP 看着各不相同,一归拢发现挤在几个 C 段里,典型的代理池特征。
- 频率过于均匀:真人的手是抖的,请求间隔忽长忽短;脚本的手稳得可怕,精确到毫秒。
- 只打接口不拉资源:真人打开页面会顺带请求 JS、CSS、图片,脚本往往闷头只锤那个接口——这几乎是判定机器流量的照妖镜。
几条一对,你手里就有了一份画像:打的是哪个接口、用的什么手法、大概来自哪个网段。这份画像,正是下一步配规则的依据。
四、第三步:长效防护,别每次都临时抱佛脚
要是每次出事都靠半夜爬起来手搓命令,这份工作迟早把人搓秃。真正省心的做法,是把防线往前挪。
上 WAF:应用层规则能识别 CC 这类异常请求,把脏流量挡在源站之外。记得花时间调规则,不然它连正常用户一起拦,那就真成了“门卫把业主锁在门外”。
频次控制与风控:按 IP、按账号、按设备指纹限频,关键接口加验证码、加签名、加一次性挑战,让机器人做一次请求的成本高到它不愿意做。
缓存能救大命:静态资源、能预渲染的页面尽量缓存,用反向代理或分布式缓存把大部分请求拦在应用之前。缓存命中一次,数据库就少受一次罪。
CDN 不只是加速:它顺带把流量摊开、就近回源,更重要的是隐藏源站真实 IP。源站 IP 一旦泄露,攻击者完全可以绕开前面的防护直捣黄龙,那时拦得再漂亮也没用。
监控和日志配齐:带宽、连接数、QPS 的水位告警提前设好,日志留够时间。没有日志,等于闭着眼睛抓贼,只能凭手感。
五、几个常见误区
- “CPU 不高,肯定不是攻击。” CC 打的是连接数和应用层慢查询,CPU 可能还很悠闲,别拿一个指标就下结论。
- “封了几个 IP 就完事。” 只能说这几个 IP 今天下班了,明天人家换个队伍接着来。
- “上了 WAF 就高枕无忧。” 规则不调会误杀,源站 IP 泄露照样被打,工具不是免死金牌。
- “扛不住就加机器。” 扩容能顶一阵,可账单也跟着涨;对方继续加量你就得继续加钱,拼的是谁钱包厚。
- “日志先不看了,救火要紧。” 火要救,但日志不看,下一场火你还会以同样的姿势被叫醒。
写在最后
CC 攻击有点像什么呢?像有人雇了一帮人守在你店门口,一人进去只问一句“你们这儿有矿泉水吗”,问完就走。单看每个人都很正常,但真正的顾客被堵在门外,寸步难行。
所以整套应急逻辑其实很朴素:先止血,让它能喘气;再看日志,认清是谁在打你;最后把 WAF、频控、缓存、CDN 这几道门装好,让防护变成日常,而不是救火。 平时把监控、限流、日志这些功课做足,半夜被叫醒的概率会小很多——当然,真出事那天闹钟还是会响,你还是得从被窝里爬起来。毕竟老板只关心一件事:早上八点,网站能不能打开。