异地多活站点面临CC攻击时,最棘手的问题不是流量有多大,而是攻击流量会被DNS轮询、Anycast或GTM策略均匀分发到多个可用区。传统单点清洗方案直接失效,因为每个节点看到的流量都不大,阈值告警根本触发不了,但业务整体已经被拖垮。解决这个问题的核心思路,是把分散在各节点的访问行为数据实时汇聚,在全局视角下做协同判定,再联动各节点执行拦截。
全域协同拦截的架构本质这套方案不是简单地把WAF规则同步到各个机房,而是要构建一个逻辑集中的决策中心。各边缘节点负责采集和上报,中心负责计算和下发指令。上报的数据维度必须足够细,至少要包含请求来源IP、访问URI、User-Agent、TLS指纹、请求时间戳、到达的节点标识。这些数据通过专线或加密隧道实时传输到决策中心,延迟必须控制在毫秒级,否则下发拦截指令时攻击可能已经结束一轮。决策中心拿到全局数据后,能立刻发现某个IP在30秒内分别向北京、上海、广州三个节点各发起了200次请求,单节点看是200次,全局看是600次,攻击特征瞬间暴露。
实时数据汇聚与去重机制全域协同最容易踩的坑是数据重复上报。同一个请求如果被多个节点同时处理,或者被CDN回源多次触发,决策中心会收到多份相同记录,导致计数虚高,正常用户被误封。必须在边缘节点上报前对请求做一次去重标记。做法是在负载均衡层注入一个全局唯一的请求ID,比如在HTTP头里插入X-Request-Id,值用雪花算法生成,包含节点编号和时间戳。决策中心收到上报数据后,以这个ID为主键做窗口去重,窗口设为5分钟,过期自动丢弃。这样即使请求在多个节点间穿透,统计到的始终是一次有效访问。
攻击特征提取与全局阈值模型传统单节点CC防护靠的是固定阈值,比如单IP每秒超过50次就封。全域场景下这个阈值完全失效,因为攻击者可以把请求速率控制在单节点阈值以下,分散到十个节点,整体QPS照样能打满后端。决策中心必须基于全局聚合后的指标重新建模。关键指标包括:全局请求速率、IP访问节点分布熵、URI访问集中度、请求间隔时序特征。一个典型的攻击特征画像是这样的:某个IP在短时间内访问了超过3个可用区,每个节点的请求速率都刚好卡在阈值下方,URI集中在搜索接口或登录接口,请求间隔呈机械式均匀分布。这种特征用单节点数据根本看不出来,但全局聚合后一目了然。决策中心可以用滑动窗口计算这些指标,窗口长度建议设为30秒,步长5秒,既能快速响应又不至于频繁抖动。
分级拦截策略与指令下发判定为攻击后,不能一刀切全部封IP,因为NAT出口或企业专线下可能混有正常用户。分级拦截是更稳妥的做法。第一级是人机验证,决策中心向所有边缘节点下发对该IP的JS挑战或验证码策略,节点收到后在响应里注入挑战脚本。第二级是限速,把该IP的全局请求速率压制到每秒5次以下,由决策中心计算配额,按比例分配到各节点执行令牌桶限流。第三级才是黑洞封禁,针对明显是代理池或扫描器的IP,直接下发null route指令到各节点的边缘路由器。指令下发通道必须独立于业务网络,建议走带外管理链路或专用的控制平面,避免攻击流量把指令通道也堵死。指令格式用Protobuf压缩,一条策略下发到全国节点能在100毫秒内完成。
节点间策略一致性保障异地多活场景下,策略同步的一致性是个硬骨头。如果北京节点已经封了某个IP,广州节点还没收到指令,攻击流量会瞬间切到广州,形成打地鼠效应。解决这个问题要用强一致性协议,比如Raft,在决策中心集群内部保证策略变更的线性一致。边缘节点作为follower,只接收已提交的日志条目。每个策略都带一个全局递增的版本号,节点定期上报自己当前应用的版本,决策中心发现某个节点落后太多就触发全量同步。同时,节点本地要缓存策略快照,重启后能立即加载,避免短暂的数据真空期。为了应对决策中心本身故障,边缘节点需要具备降级能力,当超过3秒收不到心跳时,自动切换到本地检测模式,用预设的保守阈值独立判断,保证基本防护不中断。
TLS指纹与行为链追踪CC攻击越来越偏向使用真实浏览器或Headless Chrome,单纯靠IP和UA已经很难区分。全域协同方案必须引入TLS指纹,也就是JA3或JA4哈希。攻击工具即使换了IP和UA,TLS握手时的密码套件顺序、椭圆曲线参数、扩展列表往往不变,指纹是稳定的。各节点在TLS卸载时计算指纹并上报,决策中心把指纹作为关联维度,能发现同一指纹下切换了数十个IP在攻击。更进一步,可以构建行为链:指纹关联IP群,IP群关联请求时序,形成完整的攻击团伙画像。这个画像数据可以沉淀下来,后续同一指纹再次出现时,直接进入高风险观察列表,响应速度从秒级提升到毫秒级。
配置示例:节点侧上报逻辑以下是一段边缘节点上运行的数据采集和上报逻辑的简化实现,用Go语言编写,展示核心思路:
type RequestLog struct {
RequestID string `json:"req_id"`
ClientIP string `json:"client_ip"`
URI string `json:"uri"`
UserAgent string `json:"ua"`
TLSFinger string `json:"tls_finger"`
NodeID string `json:"node_id"`
Timestamp int64 `json:"ts"`
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
reqID := r.Header.Get("X-Request-Id")
if reqID == "" {
reqID = generateSnowflakeID()
}
log := RequestLog{
RequestID: reqID,
ClientIP: getRealIP(r),
URI: r.URL.Path,
UserAgent: r.UserAgent(),
TLSFinger: getJA3Fingerprint(r.TLS),
NodeID: currentNodeID,
Timestamp: time.Now().UnixMilli(),
}
// 异步上报到决策中心,不阻塞业务响应
go reportToDecisionCenter(log)
// 继续处理业务逻辑
next.ServeHTTP(w, r)
}
这段代码的核心在于请求ID的生成和异步上报。同步上报会引入延迟,异步上报需要控制队列长度,队列满了就丢弃,优先保障业务可用性。上报的数据经过msgpack序列化后走UDP发送,牺牲少量可靠性换取极低延迟,决策中心侧做幂等处理。
决策中心聚合计算示例决策中心收到各节点上报的数据后,需要快速聚合。下面是一段基于滑动窗口的实时聚合逻辑:
type Aggregator struct {
mu sync.RWMutex
windows map[string]*Window // key: client_ip
}
type Window struct {
Buckets []int64 // 每个桶的请求计数
StartTime int64
BucketSize int64 // 桶大小,单位毫秒
}
func (a *Aggregator) Add(log RequestLog) {
a.mu.Lock()
defer a.mu.Unlock()
win, exists := a.windows[log.ClientIP]
if !exists {
win = &Window{
Buckets: make([]int64, 6), // 30秒窗口,5秒一个桶
StartTime: log.Timestamp,
BucketSize: 5000,
}
a.windows[log.ClientIP] = win
}
idx := (log.Timestamp - win.StartTime) / win.BucketSize
if idx >= 0 && idx < int64(len(win.Buckets)) {
win.Buckets[idx]++
}
}
func (a *Aggregator) IsAttack(ip string) bool {
a.mu.RLock()
defer a.mu.RUnlock()
win, exists := a.windows[ip]
if !exists {
return false
}
var total int64
for _, count := range win.Buckets {
total += count
}
// 全局阈值:30秒内超过300次
return total > 300
}
这个聚合器在内存中维护每个IP的滑动窗口,窗口长度30秒,分6个桶,每5秒滚动一次。判定阈值设为全局300次,相当于单节点平均每秒10次,分散到6个节点后单节点完全感知不到。实际生产环境中,聚合器需要做分片,按IP哈希分散到多个决策中心实例,每个实例只处理一部分IP,避免单点内存爆炸。
策略下发与边缘执行决策中心判定某个IP需要限速后,生成一条策略指令,通过gRPC流式推送到所有边缘节点。节点收到后更新本地的令牌桶配置。令牌桶的填充速率由决策中心动态计算,比如全局配额是5次/秒,有5个活跃节点,每个节点分到1次/秒。如果某个节点流量少,决策中心可以动态调配,把空闲配额分给流量大的节点,最大化利用资源。节点侧执行令牌桶的代码逻辑很轻量,不依赖外部服务,纯内存操作,延迟可以忽略不计。当IP被标记为黑洞时,节点直接调用iptables或eBPF程序在内核层丢弃数据包,连TCP握手都不建立,彻底释放CPU资源。
监控与自愈闭环方案上线后必须有完善的监控。关键指标包括:全局聚合延迟、策略下发成功率、误封率、漏封率、各节点策略版本一致性。误封率可以通过采样被封IP的后续行为来估算,比如被封后是否出现了大量同网段其他IP的异常访问,如果没有,大概率是真实用户。漏封率则需要定期回放历史攻击流量来验证。决策中心自身要做主备容灾,主节点故障时备节点通过etcd租约抢主,整个过程在2秒内完成。边缘节点与决策中心的连接断开时,除了降级到本地模式,还要持续尝试重连,重连成功后立即拉取最新策略全量快照,确保不遗漏任何封禁指令。
这套全域协同拦截方案,本质上把异地多活的架构劣势转化为了检测优势。攻击者试图用分布式请求淹没系统,反而因为多点暴露留下了更明显的全局特征。只要数据汇聚够快、判定模型够准、指令下发够稳,CC攻击在多活架构下比单点更容易被发现和阻断。
