CC攻击的本质不是拼带宽,而是拼资源。攻击者用大量看似正常的请求,耗尽你服务器的CPU、内存、数据库连接或者应用线程,让真实用户无法访问。所以防护体系的核心思路只有一条:在请求到达业务系统之前,把恶意流量识别出来并丢掉,同时尽量不误伤正常用户。下面直接讲一套从边缘到内核、从自动到人工的完整搭建方法。

第一步:先做好基础网络层防护,把能挡的挡在门外

很多人一上来就研究应用层规则,但基础网络层没做好,后面全是白搭。首先要确认你的防火墙是否开启了SYN Cookie防护。在Linux服务器上,可以检查内核参数:

sysctl net.ipv4.tcp_syncookies

如果输出是0,说明没开。执行下面的命令开启:

sysctl -w net.ipv4.tcp_syncookies=1
echo "net.ipv4.tcp_syncookies=1" >> /etc/sysctl.conf

SYN Cookie能有效缓解SYN Flood类型的CC攻击,它让服务器在收到SYN包时不立即分配完整的连接资源,而是用一个加密的Cookie来验证客户端。只有客户端完成三次握手后,服务器才真正分配资源。这个功能几乎不消耗性能,但能挡住一大类半连接攻击。

同时检查你的Web服务器是否限制了单个IP的并发连接数。以Nginx为例,在http块中加入:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn perip 20;

这表示每个IP最多同时保持20个连接。对于正常用户来说,一个浏览器同时打开的连接数通常不超过10个,20个已经足够。攻击者如果试图用单个IP发起大量并发连接,会被直接拒绝。但要注意,这个限制要放在server或location块中,并且不要设置得太低,否则会影响正常用户加载页面资源。

第二步:部署应用层频率限制,这是CC防护的核心

CC攻击最典型的特点是请求频率异常。一个正常用户浏览网页,每秒请求数通常不超过5次,而攻击脚本每秒可以发出几十甚至上百次请求。频率限制就是基于这个差异来做判断。

在Nginx中,可以用limit_req模块实现。先在http块定义规则:

limit_req_zone $binary_remote_addr zone=cc_limit:20m rate=10r/s;

然后在server或location块中应用:

limit_req zone=cc_limit burst=20 nodelay;

这段配置的意思是:每个IP每秒最多允许10个请求,允许突发20个请求,超过的部分直接返回503错误,不排队等待。nodelay参数很关键,如果不加,超出的请求会排队,反而会占用服务器资源。

但是这里有一个容易被忽略的问题:如果你的网站页面包含大量静态资源,比如一个页面加载时同时请求了20个CSS、JS和图片文件,那么10r/s的限制可能会误伤正常用户。解决办法是把频率限制只应用到动态请求上,比如PHP、API接口等。在Nginx中可以用location匹配:

location ~ \.php$ {
    limit_req zone=cc_limit burst=20 nodelay;
    # 其余PHP处理配置
}

对于纯静态资源,可以单独设置一个更宽松的限制,或者干脆不限制。这样既保护了动态业务逻辑,又不影响静态资源的加载速度。

第三步:识别攻击特征,用自定义规则精准拦截

频率限制只能挡住一部分攻击,很多CC攻击工具会模拟真实浏览器的请求头,并且控制请求频率,让频率限制难以区分。这时候需要分析攻击流量的特征,写自定义规则。

常见的攻击特征包括:固定的User-Agent字符串、缺少Referer字段、请求的URL带有随机参数、Cookie缺失或者固定不变、请求间隔高度规律等。你可以先打开Nginx的访问日志,观察攻击期间的日志:

tail -f /var/log/nginx/access.log

如果发现大量请求都带有相同的User-Agent,比如某个攻击脚本默认的UA,可以直接在Nginx中拒绝:

if ($http_user_agent ~* "sqlmap|nikto|fimap|nessus|nmap") {
    return 403;
}

如果攻击请求的URL都带有某个特定参数,比如随机数参数,可以用正则匹配:

if ($args ~* "random=\d{10}") {
    return 403;
}

但要注意,Nginx官方文档明确建议尽量避免在location中使用if,因为if指令在某些情况下会导致配置行为不符合预期。更稳妥的做法是使用map指令,在http块中定义:

map $http_user_agent $is_attack_ua {
    default 0;
    "~*sqlmap" 1;
    "~*nikto" 1;
    "~*fimap" 1;
}

然后在server块中使用:

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

这样逻辑更清晰,也避免了if嵌套带来的问题。

第四步:引入人机验证,把自动化脚本挡在业务之外

当攻击者模拟了真实的请求头和频率,单纯靠Nginx规则就很难区分了。这时候需要引入人机验证机制,让请求必须经过一道只有真人才能通过的验证。最常用的方案是JavaScript挑战,比如Cloudflare的JS Challenge或者自行实现的JS跳转。

原理很简单:当请求到达时,服务器返回一个包含JavaScript代码的页面,这段代码会执行一个计算任务,然后把结果作为Cookie或者参数重新请求。浏览器会自动执行JavaScript,所以真人用户几乎无感知,而攻击脚本如果只是简单的HTTP客户端,不会执行JS,就会被卡住。

如果你不想依赖第三方服务,可以用Nginx的Lua模块自己实现一个轻量级验证。在Nginx配置中:

location / {
    access_by_lua_block {
        local cookie = ngx.var.cookie_cc_verify
        if not cookie or cookie ~= "verified_token" then
            ngx.header["Content-Type"] = "text/html"
            ngx.say([[]])
            ngx.exit(ngx.HTTP_OK)
        end
    }
    # 正常业务处理
}

这段代码检查请求是否带有名为cc_verify的Cookie,如果没有,就返回一个设置Cookie并自动刷新的页面。浏览器会执行JS设置Cookie并重新加载,第二次请求就带上了Cookie,可以通过验证。而攻击脚本如果不解析JS,就永远无法通过。

这种方案的优势是完全自己掌控,不依赖外部服务,而且对用户几乎无感。缺点是攻击者如果专门针对你的验证逻辑写脚本,也能绕过。所以它适合作为一道额外的防线,而不是唯一的防线。

第五步:利用缓存和CDN分担压力,让攻击流量到不了源站

如果你的网站有大量静态内容,或者页面可以被缓存,那么把缓存层前置是抵御CC攻击最有效的手段之一。攻击请求如果命中了缓存,源站根本不会处理,攻击就失去了意义。

在Nginx中配置反向代理缓存:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=page_cache:50m max_size=2g inactive=60m use_temp_path=off;

server {
    location / {
        proxy_cache page_cache;
        proxy_cache_key "$scheme$request_method$host$request_uri";
        proxy_cache_valid 200 301 302 10m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_pass http://backend;
    }
}

这段配置把后端返回的200、301、302响应缓存10分钟。在这10分钟内,相同的请求直接由Nginx返回缓存内容,后端服务器完全不参与。即使攻击者发起海量请求,只要请求的URL相同,Nginx就能扛住。

但缓存只对可缓存的页面有效。如果你的网站有大量个性化内容,比如用户登录后的页面,缓存就不适用了。这时候可以考虑把登录和个性化功能分离,让未登录用户看到的页面走缓存,登录后的请求走动态处理。攻击者通常不会登录,所以攻击流量大部分会命中缓存。

第六步:建立监控和自动响应机制,让防护体系动态运转

静态的规则总有被绕过的时候,所以必须建立实时监控和自动响应机制。核心思路是:当检测到异常流量时,自动触发更严格的防护策略,攻击结束后自动恢复。

一个简单的实现方案是用脚本分析Nginx日志,统计每个IP的请求频率和状态码分布。比如用awk统计每分钟请求数排名前20的IP:

awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

如果某个IP的请求数远超正常水平,或者大量请求返回了非200状态码,就可以判定为攻击源,自动将其加入防火墙黑名单。用iptables封禁:

iptables -A INPUT -s 攻击IP -j DROP

把这个逻辑写成脚本,用crontab每分钟执行一次,就形成了一个简单的自动封禁系统。更进一步,可以用fail2ban这样的工具来管理封禁规则,它支持自动解封和日志轮询,配置起来更灵活。

监控的另一个维度是服务器资源使用情况。当CPU使用率持续超过80%,或者负载突然飙升,即使还没有明确的攻击特征,也应该触发应急策略,比如临时提高频率限制的严格程度,或者开启更激进的人机验证。可以用uptime和top命令配合脚本判断:

load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
if [ $(echo "$load > 10" | bc) -eq 1 ]; then
    # 触发应急防护策略
fi

第七步:业务层面的纵深防御,把攻击影响降到最低

即使前面的防护都失效了,业务层面还可以做很多事来降低攻击的影响。比如数据库查询优化,确保单次查询时间足够短;对热点数据做本地缓存,减少数据库压力;对耗时的操作做异步处理,避免请求线程被长时间占用。

一个常见的CC攻击目标是搜索功能,因为搜索通常涉及数据库全文检索,非常消耗资源。攻击者会不断提交搜索请求,把数据库拖垮。解决方案是对搜索接口做独立的频率限制,并且对搜索结果做缓存:

location /search {
    limit_req zone=search_limit burst=5 nodelay;
    proxy_cache search_cache;
    proxy_cache_valid 200 5m;
    proxy_pass http://backend;
}

同时,在应用代码中增加超时控制,比如数据库查询超过2秒就强制返回空结果,避免请求堆积。这些措施虽然不能直接阻止攻击,但能让服务器在遭受攻击时保持部分可用,为后续处理争取时间。

总结:防护体系是分层的,每一层都有明确的作用

完整的CC攻击防护体系由网络层、应用层、验证层、缓存层、监控层和业务层共同构成。网络层的SYN Cookie和连接数限制挡住最基础的攻击;应用层的频率限制和自定义规则拦截大部分自动化流量;人机验证区分真人和脚本;缓存和CDN让攻击流量无法触达源站;监控和自动响应让防护动态调整;业务层的优化则保证即使被突破也能维持基本服务。

搭建这套体系不需要昂贵的商业设备,用开源的Nginx、iptables和简单的脚本就能实现。关键是每一层都要根据自己网站的实际流量特征来调参,而不是照搬别人的配置。先观察正常流量的基线,再设置阈值,最后用模拟攻击来验证效果,这样才能得到一个真正有效的防护体系。