首页 / 帮助文档 / 通过访问频率突变检测CC攻击的算法

通过访问频率突变检测CC攻击的算法

CC攻击的核心手法,就是利用大量傀儡机或代理IP,在短时间内对目标URL发起海量看似正常的HTTP请求,耗尽服务器资源。传统的基于固定阈值(如每秒请求数)的防御手段,在面对精心构造的低频慢速攻击时常常失效。攻击者会把请求频率控制在阈值之下,让每个IP看起来都像一个正常用户,但成千上万个这样的“正常用户”叠加起来,照样能把服务器压垮。要解决这个问题,我们必须跳出“看绝对值”的思维,转向“看变化趋势”。通过访问频率突变检测算法,我们能精准识别那些行为模式突然发生剧烈变化的访客,这种变化本身就是最强烈的异常信号。

为什么频率突变比固定阈值更有效

固定阈值防御就像给一扇门设置了一个承重极限:超过100公斤的人不许进。攻击者可以找来100个99公斤的人,照样把门堵死。而频率突变检测不看访客的绝对“体重”,而是看他的“体重”在短时间内是否突然暴增或暴减。一个正常用户,其访问频率通常呈现平稳的随机分布,可能偶尔会快一些,但绝不会在1分钟内从每分钟5次请求突然飙升到每分钟50次,并持续维持。这种数学特征上的断层,就是我们检测的黄金切入点。该方法的优势在于无需维护庞大的攻击特征库,对未知的CC变种攻击同样有效,因为它捕捉的是攻击行为本身必然带来的统计学特征变化。

核心算法:基于滑动窗口的突变评分模型

要实现这个检测逻辑,我们需要构建一个轻量级但高效的统计算法。算法的核心思想是为每个IP维护一个时间序列的访问频率数据,通过比较近期短窗口的频率与历史长窗口的频率分布,计算出一个“突变评分”。当这个评分超过动态阈值时,判定为异常。整个模型可以拆解为三个关键环节:数据采集与窗口划分、突变特征量化、动态基线自学习。

数据采集与双窗口机制

我们不需要解析HTTP报文的内容,只需要在网关层或应用层以极低的代价记录每个IP的访问时间戳。算法维护两个滑动窗口:一个短窗口(例如1分钟),用于捕捉当前实时频率;一个长窗口(例如30分钟或1小时),用于建立该IP的历史行为基线。长窗口又被切分为多个与短窗口等长的时间片。假设短窗口为1分钟,长窗口为30分钟,那么长窗口就由30个连续的时间片组成。每当一个新的时间片结束时,短窗口的数据会滑入长窗口,长窗口最旧的一个时间片数据会被丢弃,以此保证基线能够自适应流量的自然变化。

突变评分的计算方法

突变评分的计算不是简单地比较短窗口频率是否高于长窗口平均值,因为正常流量也存在波动。我们需要引入统计学的概念来衡量这种偏离是否“显著”。一种计算效率高且效果好的方法是使用改进的Z-Score或基于泊松分布的概率评分。这里我们给出一种基于泊松累积概率的评分逻辑,因为HTTP请求在时间上的分布近似符合泊松过程。具体步骤如下:

1. 计算长窗口内每个时间片的平均请求数,记为λ(lambda),它代表该IP的历史平均访问速率。

2. 获取当前短窗口内的实际请求数,记为k。

3. 计算在期望值为λ的泊松分布中,随机变量X大于等于k的概率P(X >= k)。这个概率值越小,说明k偏离λ越显著。

4. 为了便于比较,我们取这个概率的负对数作为突变评分:Score = -log10(P(X >= k))。如果k远大于λ,概率会极其接近0,评分会急剧升高。例如,一个IP历史平均每分钟请求5次,当前窗口突然达到20次,其泊松累积概率极低,评分会轻松超过警戒线。

以下是一段简化的Python伪代码,用于演示评分计算的核心逻辑:

import math
from collections import deque

class BurstScoreCalculator:
    def __init__(self, short_window_sec=60, long_window_slices=30):
        self.short_window = short_window_sec
        self.long_slices = long_window_slices
        self.history = deque(maxlen=long_window_slices)  # 存储历史每个时间片的请求数
        self.current_counter = 0  # 当前短窗口的计数器

    def add_request(self):
        self.current_counter += 1

    def calculate_score(self):
        if len(self.history) == 0:
            return 0.0
        
        # 计算历史平均速率λ
        lam = sum(self.history) / len(self.history)
        if lam == 0:
            lam = 0.1  # 防止除零,给一个极小的平滑值
        
        k = self.current_counter
        
        # 计算泊松分布的累积概率 P(X >= k)
        # 使用正则化不完全伽马函数的简化近似,或直接循环累加
        prob = 0.0
        for i in range(k):
            prob += (lam  i) * math.exp(-lam) / math.factorial(i)
        tail_prob = 1 - prob
        
        if tail_prob <= 0:
            # 概率极低,给一个很大的评分上限
            return 10.0
        
        score = -math.log10(tail_prob)
        return score

    def rotate_window(self):
        # 时间片结束时调用,将当前计数器存入历史,并重置
        self.history.append(self.current_counter)
        self.current_counter = 0

这段代码展示了算法的骨架。在实际部署中,你需要为每个IP维护一个这样的计算器实例,并通过定时任务触发窗口轮转和评分计算。

动态基线与自适应阈值

固定的评分阈值(如Score > 3)会带来大量误报,因为不同业务场景、不同时段的流量模式差异巨大。凌晨的流量基线很低,一个微小的绝对增量就可能导致评分飙升;而促销活动期间,所有用户的访问频率都会整体上扬。因此,阈值必须是动态的。我们可以采用一种简单而有效的策略:不仅计算单个IP的突变评分,还计算该IP所属“群体”的整体波动水平。比如,以IP所在的C段或地域为维度,计算该群体内所有IP突变评分的中位数和标准差。只有当某个IP的评分显著高于其所在群体的平均水平(例如,超过群体中位数3个标准差)时,才触发防御动作。这样,促销期间的整体流量上涨会被群体基线吸收,不会导致大规模误报,而混在其中的攻击IP因其行为突变远超正常用户,依然会被精准揪出。

工程化落地时的关键优化

理论模型要落地到高并发环境,必须解决内存和计算开销问题。如果为每一个访问IP都维护一个长窗口队列,在遭遇大规模DDoS或存在海量真实用户时,内存会迅速膨胀。这里必须引入分级淘汰与概率处理机制。第一级,使用布隆过滤器或计数型布隆过滤器,只对高频访问的IP分配完整的窗口计算资源。对于每分钟只有一两次请求的IP,直接放过或仅做简单计数。第二级,在请求量极大时,对请求进行采样,比如只对10%的请求进行计数和窗口更新,通过统计学补偿来还原真实频率,这在牺牲极小精度的前提下能大幅降低资源消耗。第三级,窗口轮转和评分计算不应在请求的实时链路上同步进行,而应交由后台异步任务批量处理,确保业务响应延迟不受影响。

与现有防御体系的协同工作

频率突变检测算法不是一个孤立的安全模块,它最适合作为传统限速和WAF之间的一个智能过滤层。当算法判定某个IP的访问频率发生突变时,不要立即永久封禁,因为可能误伤到刚刚开始观看直播或抢购商品的真实用户。更合理的处置是触发“人机验证挑战”,例如弹出滑块验证码或执行JS算法挑战。如果挑战通过,将该IP的当前高频状态主动注入到长窗口历史中,让基线快速学习这个新的行为模式,避免后续持续误报。如果挑战失败,则将IP交给WAF进行更细致的恶意Payload分析,或者直接进行短时拉黑。这种分层处置策略,兼顾了安全性与用户体验。

从单维频率到多维行为突变

掌握了基于频率突变的检测思想后,我们可以将其扩展到更广阔的维度。攻击者在发起CC攻击时,除了请求频率,其请求的URL分布、User-Agent分布、请求参数离散度等行为特征,往往也会在攻击瞬间发生突变。例如,一个平时只浏览首页和列表页的IP,突然开始高频率请求同一个搜索接口;或者一个IP的User-Agent在短时间内发生了数次变化。将这些维度也纳入突变评分体系,为每个维度计算一个突变分数,最后通过加权求和或机器学习模型进行综合判定,可以构建出极其鲁棒的异常检测系统。这种多维度突变分析,是应对未来更智能、更拟人化CC攻击的核心思路。