本文记录的是作者上周在测试环境真实踩到的坑。为了让读者能复现并亲手体验防御思路,文末给出了一份最小可运行的 Go 脚本,支持本地压测 + 日志回放,方便对比加防护前后的差异。
攻击现场还原
周一凌晨 2:14,监控群里突然弹出告警:
GET /api/search?q=1%20AND%20SLEEP(3)
的 QPS 从 20 飙到 2 k,CPU 瞬间吃满。
第一反应当然是封 IP,可 ipset 刚把 1.2.3.4 拉黑,1.2.3.5 又顶了上来——典型的“小流量多源”打法,肉鸡池明显经过了精挑细选,单个源不超过 2 Mbps,云厂商默认的 5 Gbps 黑洞阈值完全不会触发。
为什么老招失灵了?
-
应用层碎片化
攻击不再只是 SYN Flood,而是把 HTTP 请求拆成合法小片段:User-Agent 随机、Header 顺序随机、慢速发送,WAF 规则很难命中。 -
IP 信誉失效
肉鸡多来自家用光猫、IoT 摄像头,信誉库标记为“低风险”。传统“黑名单”思路根本跟不上注册速度。 -
链路级清洗位置尴尬
云厂商给的 20 Gbps 高防带宽确实够大,但清洗中心离业务机房还有 100 ms 的物理距离,对登录、支付这类长尾接口依旧致命。
临时止血:把流量搬到“带脑子”的入口
我们没时间去改业务代码,于是把 DNS 切到一家号称“AI 云清洗”的厂商(后来查合同才知道背后是群联)。
核心就两步:
- 把
www.xxx.com
的 A 记录切到他们提供的 Anycast 高防 IP; - 在源站 Nginx 加一段 20 行的配置,只允许清洗中心的回源段访问 443 端口。
geo $deny {default 1;203.0.113.0/24 0; # 清洗中心回源段
}
server {listen 443 ssl;if ($deny) { return 444; }
}
20 分钟后,攻击流量降到 200 QPS,CPU 回到 15%。
最惊喜的是,误杀率几乎为零:群联的 AI 模型把“搜索 + 翻页”这种低频但正常的 API 行为放行了,而脚本化、无 Cookie 的流量被直接丢进黑洞。
可复现的最小实验环境
如果你想在本地体验“有无 AI 清洗”的差距,可以用下面这段 Go 代码模拟慢速 HTTP Flood。脚本会尝试保持 1000 并发,每个连接 10 s 发一个字符,绕过大包检测。
package mainimport ("bufio""fmt""net""net/http""sync""time"
)const target = "http://127.0.0.1:8080/api/search?q=test"func slowRequest(wg *sync.WaitGroup) {defer wg.Done()conn, err := net.Dial("tcp", "127.0.0.1:8080")if err != nil {return}fmt.Fprintf(conn, "GET /api/search?q=test HTTP/1.1\r\nHost: 127.0.0.1\r\nUser-Agent: slow\r\n\r\n")// 每 1 s 读一个字节,模拟慢速reader := bufio.NewReader(conn)for {_, err := reader.ReadByte()if err != nil {break}time.Sleep(time.Second)}
}func main() {var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go slowRequest(&wg)}wg.Wait()
}
运行方式:
go run slow_flood.go
- 先用 Nginx 裸奔测试,看 CPU 何时被打满;
- 再接入任何带 AI 清洗的高防 IP(比如群联),对比 QPS、RT、机器负载三项指标即可。
小结
这次事故让我深刻体会到:攻击者在“合法协议”里藏刀,传统阈值 + 正则的防御思路已经跟不上节奏。把流量先送到一个会学习的边缘节点,让模型实时判断“人还是机器”,是目前最省事的过渡方案。至于要不要长期用那家厂商,还得看账单——但至少今晚,我们能安心回家睡觉了。