CC攻击的可怕之处在于,它并不靠海量的流量带宽取胜,而是利用大量看似正常的并发请求,精确打击数据库查询密集或运算复杂的URL,耗尽服务器资源。传统基于IP速率的封禁策略在面对遍布代理池和肉鸡的慢速CC攻击时,往往力不从心。攻击者可以把请求速率控制在阈值之下,伪装成正常用户,导致正则匹配型的防御规则失效。要解决这个问题,必须从请求行为的“智能”分析入手,而不是单纯数数。

核心防御逻辑:从“看门狗”到“行为分析师”

Nginx作为高性能反向代理,其日志记录了每个请求的精确画像:时间、URI、参数、状态码和上游响应时间。Fail2ban的强大之处在于其灵活的过滤机制,但传统用法只盯着IP和访问频率。我们要做的是让Fail2ban读懂Nginx的日志语义,把“高频访问”升级为“异常行为模式识别”。

真正的智能拦截,核心在于定义“恶意特征”。一个典型的CC攻击流量往往具备以下指纹:大量请求集中在少数几个动态页面(如搜索接口、登录口、API端点);后端响应时间异常增长,因为CC攻击导致PHP-FPM或数据库队列堵塞;在一段时间窗口内,特定IP对特定URL的请求次数远超人类浏览极限,但总请求量可能并不高;请求间隔极其规律,或者User-Agent异常单一。

我们的策略是:不拦截所有高频IP,只拦截那些“把服务器搞慢了”的IP。这是最精准的切入点。

Nginx日志的深度定制:记录上游响应时间

默认的Nginx日志格式往往缺少关键的上游响应时间($upstream_response_time)和请求处理时间($request_time)。要实现智能拦截,必须先修改Nginx的日志格式。打开nginx.conf,定义一个名为“cc_analytics”的日志格式:

log_format cc_analytics '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'rt=$request_time uct="$upstream_connect_time" '
                        'uht="$upstream_header_time" urt="$upstream_response_time" '
                        'host="$host"';

这里最关键的是$upstream_response_time,它记录了Nginx将请求转发给后端(如PHP-FPM)后,等待后端处理完成并返回响应的时间。当这个数值从正常的0.0x秒飙升到数秒甚至超时,就是服务器被CC攻击打满的明确信号。

将这个日志格式应用到你的主站点配置中:

access_log /var/log/nginx/access.log cc_analytics buffer=32k;

重载Nginx后,日志里就会出现类似“urt=3.452”的字段,这代表后端处理这个请求花了3.4秒。这就是我们要抓取的“罪证”。

Fail2ban的“读心术”:编写智能过滤规则

Fail2ban的过滤器不能只做简单的正则计数,我们需要利用它的多行匹配和复杂条件组合能力。在/etc/fail2ban/filter.d/目录下新建一个名为nginx-cc-smart.conf的文件。

我们要定义一个组合攻击特征:一个IP在60秒内,对特定动态脚本(如.php或/api/路径)发起了超过15次请求,并且其中至少有5次请求的上游响应时间超过2秒。这种组合条件能极大降低误封概率,因为正常的搜索引擎爬虫或API调用虽然频率高,但不会导致后端响应时间集体飙升。

[Definition]
failregex = ^<HOST> -.*"(GET|POST) .*(\.php|/api/).*" .* urt=[2-9]\.\d+.*$
            ^<HOST> -.*"(GET|POST) .*(\.php|/api/).*" .* urt=\d{2,}\.\d+.*$

ignoreregex =

这个正则表达式的设计非常讲究。它匹配所有包含“.php”或“/api/”路径的请求,并且urt(上游响应时间)大于等于2秒。注意我们用了两条规则:一条匹配2到9秒,另一条匹配两位数以上的秒数,确保覆盖所有慢响应。Fail2ban会自动对匹配到的行进行计数。

但这只是过滤器,真正的“智能”在于jail的配置。打开/etc/fail2ban/jail.local,我们需要精心调校参数:

[nginx-cc-smart]
enabled = true
port = http,https
filter = nginx-cc-smart
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 60
bantime = 3600
action = iptables-multiport[name=CC, port="http,https", protocol=tcp]
         nginx-block-map

这里maxretry设为5,findtime设为60秒,意味着如果某个IP在1分钟内触发了5次“慢响应”请求,就会被封禁1小时。这比单纯限制请求数要精准得多。一个正常用户即使短时间内点了很多页面,只要服务器健康,响应时间都在毫秒级,绝不会触发这个规则。而攻击者一旦造成服务器卡顿,他自己的请求响应时间也会变长,从而立刻自投罗网。

高阶技巧:Nginx实时黑名单联动

直接用iptables封IP虽然有效,但会消耗系统资源,且封禁列表一长,iptables规则遍历就会变慢。更优雅的方案是让Fail2ban将攻击IP写入一个文本文件,由Nginx在请求处理阶段直接返回403。这几乎不消耗资源,且响应速度极快。

在Fail2ban的action.d目录下新建nginx-block-map.local:

[Definition]
actionstart = 
actionstop = 
actioncheck = 
actionban = echo '<ip> 1;' >> /etc/nginx/conf.d/blockips.map
actionunban = sed -i '/^<ip> 1;$/d' /etc/nginx/conf.d/blockips.map

然后在Nginx的http段中定义一个名为$blocked_ip的变量,通过map指令读取这个文件:

map $remote_addr $blocked_ip {
    include /etc/nginx/conf.d/blockips.map;
    default 0;
}

在server段中,只需要一行判断:

if ($blocked_ip = 1) {
    return 403;
}

这样一来,被封禁的IP请求在Nginx层面就被直接拦截,连后端应用的门都摸不到。当解封时间到,Fail2ban执行actionunban,自动从文件中删除该IP,Nginx无需重载即可感知变化,因为map指令会在每次请求时动态读取文件内容(或根据缓存机制更新)。

对抗分布式慢速攻击:请求体速率限制

还有一种更隐蔽的CC变种,攻击者会故意以极慢的速度发送请求体,比如一个POST请求的Content-Length是10MB,但攻击者每秒只发几个字节,让Nginx的工作进程一直阻塞在读操作上,耗尽连接池。

针对这种攻击,Nginx自身的$request_time无法完全反映问题,因为时间主要消耗在接收请求体上。我们需要结合Nginx的client_body_timeout和client_header_timeout指令,同时利用Fail2ban监控Nginx的错误日志。在错误日志中,慢速攻击往往会留下“client timed out”或“client sent too long URI”等记录。

新增一个针对错误日志的过滤器:

[Definition]
failregex = ^<HOST> .* (client timed out|client sent too long URI|client body timeout).*$

这种攻击的特征是同一个IP会反复触发超时错误,设置maxretry=3,findtime=120,就能有效清除这些占用连接资源的“僵尸”连接。

实战中的阈值调优与误伤规避

任何自动化防御系统最大的敌人都是误伤。在部署这套规则后,必须进行一段时间的“观察模式”。你可以先将action设置为仅仅记录日志或发送告警,而不执行实际封禁。通过分析触发的日志,调整urt的时间阈值。如果你的应用本身就有一些需要长处理的合法请求(如报表导出),就需要在过滤器中通过URL前缀进行排除。

例如,在failregex中增加否定预查,排除掉“/admin/export/”路径:

failregex = ^<HOST> -.*"(GET|POST) (?!\/admin\/export\/).*(\.php|/api/).*" .* urt=[2-9]\.\d+.*$

另外,对于移动端网络或企业NAT出口,IP共享情况普遍,封禁时间不宜过长。bantime可以逐步递增,第一次10分钟,第二次1小时,第三次24小时,使用Fail2ban的bantime.increment功能可以轻松实现。

最后,结合Nginx的limit_req模块做第一道防线,Fail2ban做第二道行为分析防线,两者配合能覆盖从洪水攻击到慢速CC的绝大部分场景。limit_req处理突发流量,Fail2ban处理绕过速率限制的智能化攻击,这种纵深防御体系是目前在应用层对抗CC攻击最具性价比的方案,无需依赖昂贵的云清洗服务,利用服务器自身算力就能解决80%以上的攻击问题。