网站遭遇CC攻击时最典型的特征不是带宽跑满,而是服务器CPU和数据库连接数在短时间内飙到极限,但单个IP的请求量看起来并不夸张。CC攻击的本质是模拟大量真实用户对动态页面、搜索接口、数据库查询等消耗服务器资源的URL发起请求,让服务器忙于处理这些高消耗请求而无法响应正常访客。攻击者控制成千上万台肉鸡或者代理IP,每个IP只发几十个请求,传统的单IP限速策略很难拦截。所以应急处置的第一步不是封IP,而是先判断攻击目标URL和请求特征,把高消耗的动态请求与静态资源分离。

第一步:快速定位攻击目标和特征

登录服务器后先查看Web服务器当前并发连接数和请求状态。Nginx环境执行以下命令可以看到当前连接数最高的客户端IP和请求的URL:

netstat -an | grep :80 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head -20

Apache环境可以查看mod_status页面,或者用以下命令统计当前访问最频繁的IP:

tail -n 10000 /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -20

如果发现大量请求集中在某个搜索接口、登录接口、验证码刷新接口或者带复杂查询参数的URL上,基本可以确定攻击目标。记录这些URL和请求参数特征,后续的防护策略都要围绕这些特征展开。不要一上来就封IP,因为攻击者会不断更换IP,封IP的效率很低,而且容易误伤正常用户。

第二步:启用应急限速和连接数限制

Nginx环境下最有效的应急手段是对攻击目标URL做精确限速。在server块中加入以下配置,对特定接口限制每秒请求数:

location /search {
    limit_req zone=cc burst=5 nodelay;
    limit_req_status 503;
}

limit_req_zone $binary_remote_addr zone=cc:10m rate=1r/s;

这段配置的意思是每个IP每秒最多请求一次/search接口,突发最多5个请求,超过直接返回503。limit_req_zone要放在http块中,limit_req放在对应的location中。如果攻击目标是整个网站而不是某个接口,可以对全局做连接数限制:

limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    limit_conn addr 10;
}

这个配置限制每个IP同时最多保持10个连接。CC攻击往往需要保持大量并发连接来消耗服务器资源,限制连接数能有效降低服务器压力。Apache环境可以用mod_evasive模块做类似的限速和连接数控制,或者在前端加一层Nginx做反向代理来统一处理限速。

第三步:利用缓存扛住攻击流量

CC攻击打的是动态请求,因为动态请求要消耗数据库和CPU。如果能把动态请求的结果缓存起来,攻击请求就不会穿透到后端数据库。对于不涉及用户个人数据的页面,比如首页、列表页、文章详情页,可以在Nginx中配置代理缓存:

proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=my_cache:100m max_size=1g inactive=60m use_temp_path=off;

server {
    location / {
        proxy_cache my_cache;
        proxy_cache_valid 200 60s;
        proxy_cache_key "$scheme$request_method$host$request_uri";
        proxy_pass http://backend;
    }
}

这样配置后,同一个URL在60秒内的重复请求会直接返回缓存内容,不会打到后端应用服务器。攻击者即使发起大量请求,Nginx也只是从缓存中读取数据返回,对后端数据库和CPU的消耗几乎为零。缓存时间要根据网站内容更新频率来调整,新闻类网站可以设置短一些,企业官网可以设置长一些。如果网站本身有Redis,也可以把热点数据缓存到Redis中,应用层先查Redis再查数据库。

第四步:识别并阻断代理IP和攻击特征

CC攻击大量使用代理IP和云主机IP,这些IP通常不属于正常用户所在的网段。通过分析访问日志,可以提取攻击请求的User-Agent特征。很多CC攻击工具使用固定的User-Agent字符串,比如一些脚本默认的UA是python-requests、Go-http-client或者空UA。在Nginx中可以直接根据UA特征拦截:

if ($http_user_agent ~* "python-requests|Go-http-client|curl|wget|^$") {
    return 444;
}

对于IP层面的识别,可以借助第三方威胁情报库或者开源工具来检测代理IP。如果攻击来源集中在某些IP段,可以直接在防火墙层面封禁整个段。Linux下用iptables封禁IP段:

iptables -I INPUT -s 192.168.0.0/16 -j DROP

但要谨慎操作,先确认这些IP段确实不属于正常用户来源。更精细的做法是对访问频率异常的IP自动封禁。可以用fail2ban配合Nginx日志实现自动封禁,在jail.local中配置:

[nginx-cc]
enabled = true
filter = nginx-cc
action = iptables-multiport[name=nginx-cc, port="http,https", protocol=tcp]
logpath = /var/log/nginx/access.log
maxretry = 100
findtime = 60
bantime = 600

这个配置的意思是60秒内同一个IP请求超过100次就自动封禁10分钟。阈值要根据网站正常访问量来调整,避免误伤。

第五步:前端接入高防服务进行流量清洗

如果攻击流量已经超过了服务器带宽或者连接数上限,服务器层面的防护已经无法解决问题,需要把流量引导到高防服务进行清洗。高防服务通过分布式的清洗节点识别攻击流量,只把正常流量回源到服务器。接入高防服务后,DNS解析指向高防服务的CNAME地址,攻击流量先经过清洗中心,正常请求才会到达源站。这种方式对大型CC攻击最有效,但需要提前准备,临时接入会有DNS生效时间的问题。选择高防服务时重点看清洗节点的分布和CC防护策略是否支持自定义规则,有些高防服务对CC攻击的识别能力较弱,需要手动配置URL白名单和频率阈值。

防护加固的长期策略

应急处置解决的是当下问题,长期防护需要从架构和代码层面做加固。第一个要解决的问题是动态请求和静态请求分离。把静态资源放到CDN上,源站只处理动态请求,这样即使动态请求被打,静态资源仍然可以正常访问。第二个要解决的问题是接口的幂等性和资源消耗控制。搜索接口、列表接口这类高消耗接口要设置合理的分页大小,限制单次请求返回的数据量。对于需要登录才能访问的接口,在应用层做用户级别的频率限制,而不是只依赖IP限速。第三个要解决的问题是验证码的合理使用。在登录、注册、搜索等关键接口上增加验证码,但验证码本身也会消耗服务器资源,所以要选择轻量级的验证码方案,比如滑块验证或者无感验证,避免使用需要大量图片处理的传统字符验证码。

架构层面建议在源站前部署一层反向代理集群,统一处理限速、缓存、黑白名单等策略。反向代理层可以横向扩展,即使单台代理节点被打满,也可以快速增加节点分担压力。数据库层面要做好读写分离和连接池配置,限制单个应用实例的最大数据库连接数,防止CC攻击导致数据库连接被占满。监控层面要建立完善的告警机制,对CPU、内存、连接数、请求量等指标设置阈值告警,在攻击发生的初期就能收到通知并介入处理,而不是等网站完全不可访问了才发现。

CC攻击的防护没有一劳永逸的方案,攻击者会不断调整攻击方式和特征。日常运维中要定期分析访问日志,了解网站的正常访问模式和流量基线,这样在攻击发生时才能快速判断异常。同时要保持Web服务器、应用框架和操作系统的安全更新,很多CC攻击利用的是应用层的性能漏洞,比如某些框架的特定接口存在低效查询,攻击者只需要针对这个接口发起请求就能造成拒绝服务。代码审计和性能测试要纳入常规开发流程,从源头上减少高消耗接口的数量。