CC攻击的可怕之处不在于流量有多大,而在于它模拟的是正常请求。一个攻击请求和一个真实用户点击,在TCP层几乎看不出区别。传统防护思路是堆硬件、买高防IP、接云防护,但中小站长和独立开发者往往没有这个预算。实际上,在Nginx或Apache这一层,配合内核参数和简单的应用层逻辑,就能构建一套相当有效的CC基础防护体系,完全不需要安装任何第三方插件或模块。

理解CC攻击的流量特征

CC攻击本质上是对动态页面的高频请求,目标通常是搜索接口、登录页面、API端点这类消耗服务器资源的URL。攻击流量有三个明显特征:请求频率异常高、User-Agent高度一致或使用常见爬虫标识、请求来源IP集中在少数几个段甚至单IP。识别这些特征不需要机器学习,用Nginx原生的limit_req模块配合map就能精准命中。

很多人一上来就限制全站请求频率,这是错误的。静态资源如图片、CSS、JS不应该被限制,否则正常用户浏览页面时会触发大量误封。正确的做法是只对动态页面和敏感接口做频率限制,静态资源交给expires头和CDN处理。

Nginx无插件防护的核心配置

Nginx自带的ngx_http_limit_req_module和ngx_http_limit_conn_module是两个极其强大的模块,编译时默认包含。limit_req基于漏桶算法控制请求速率,limit_conn控制并发连接数。先定义共享内存区域,然后在需要防护的location中引用。

http {
    # 定义请求频率限制区域,10m内存可存储约16万个IP状态
    limit_req_zone $binary_remote_addr zone=dynamic:10m rate=10r/s;
    
    # 定义并发连接限制区域
    limit_conn_zone $binary_remote_addr zone=connlimit:10m;
    
    server {
        location / {
            # 静态资源直接返回,不做限制
            location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
                expires 30d;
                add_header Cache-Control "public, immutable";
            }
        }
        
        # 动态页面和API做频率限制
        location ~* \.(php|asp|aspx|jsp)$ {
            limit_req zone=dynamic burst=20 nodelay;
            limit_conn connlimit 10;
            # 其他fastcgi或proxy配置...
        }
        
        location /api/ {
            limit_req zone=dynamic burst=5 nodelay;
            limit_conn connlimit 5;
            # 其他代理配置...
        }
    }
}

burst参数是关键。burst=20表示允许20个请求排队,nodelay表示超出rate的请求立即处理而不延迟。如果不加nodelay,合法用户在短时间内密集点击时会感到明显延迟。rate=10r/s意味着每秒最多处理10个请求,超出部分根据burst设置要么排队要么拒绝。这个数值需要根据实际业务调整,一个普通动态页面的正常用户浏览频率通常不超过每秒2-3个请求。

利用map和if实现智能封禁

单纯限制频率会误伤共享IP的企业用户或学校网络。更精细的做法是结合User-Agent和请求特征进行判断。Nginx的map指令可以在不安装任何模块的情况下创建变量映射,结合if指令实现条件判断。

http {
    # 定义恶意UA特征
    map $http_user_agent $is_bad_ua {
        default 0;
        "~*curl" 1;
        "~*wget" 1;
        "~*python" 1;
        "~*scrapy" 1;
        "~*Go-http-client" 1;
        "~*fasthttp" 1;
        "" 1;  # 空UA直接标记
    }
    
    # 定义需要重点防护的URL
    map $uri $is_sensitive {
        default 0;
        "~*search" 1;
        "~*login" 1;
        "~*register" 1;
        "~*admin" 1;
        "~*wp-login" 1;
        "~*xmlrpc" 1;
    }
    
    server {
        # 恶意UA直接返回444(Nginx专用状态码,直接关闭连接)
        if ($is_bad_ua = 1) {
            return 444;
        }
        
        location / {
            # 敏感URL使用更严格的频率限制
            if ($is_sensitive = 1) {
                set $limit_rate_key "sensitive";
            }
            # 其他配置...
        }
    }
}

返回444状态码是Nginx特有的处理方式,它直接关闭TCP连接而不返回任何HTTP响应,攻击脚本通常会因为连接被重置而报错退出,比返回403更有效。空User-Agent几乎可以肯定是脚本,正常浏览器都会发送UA字符串。

内核层面的TCP参数优化

应用层防护做得再好,如果内核TCP队列被打满,服务器照样无法响应。Linux内核有几个参数直接影响抗CC能力,这些参数不需要安装任何工具,用sysctl就能调整。

# /etc/sysctl.conf 或 /etc/sysctl.d/99-anti-ddos.conf

# 启用SYN Cookie,防止SYN Flood耗尽半连接队列
net.ipv4.tcp_syncookies = 1

# 缩短SYN_RECV状态的超时时间,默认60秒改为10秒
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2

# 加快TIME_WAIT状态回收
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# 扩大半连接队列和全连接队列
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192

# 启用TCP时间戳,提高连接复用效率
net.ipv4.tcp_timestamps = 1

# 减少孤儿连接占用的资源
net.ipv4.tcp_max_orphans = 65536
net.ipv4.tcp_orphan_retries = 2

SYN Cookie是最基础的防SYN Flood机制,几乎必须开启。tcp_synack_retries控制服务器发送SYN-ACK后等待客户端ACK的次数,默认5次意味着最长等待时间可能达到180秒,攻击者可以利用这点耗尽服务器资源。改为2次后,等待时间缩短到十几秒,能快速释放被占用的半连接资源。

tcp_tw_reuse需要和tcp_timestamps配合使用,它允许将处于TIME_WAIT状态的连接用于新的TCP连接。高并发场景下,TIME_WAIT状态的连接数量惊人,这个参数能显著提升连接复用率。但要注意,在NAT环境下tcp_tw_recycle已经不建议使用,这里只开启tcp_tw_reuse。

应用层请求指纹识别

CC攻击工具通常不会执行JavaScript,也不会正确设置浏览器特有的请求头。利用这一点,可以在不依赖Cookie和JS挑战的情况下识别大部分脚本请求。Nginx可以通过检查请求头的完整性来判断请求是否来自真实浏览器。

server {
    # 检查浏览器特有的请求头
    set $suspicious 0;
    
    # 真实浏览器都会发送Accept头
    if ($http_accept = "") {
        set $suspicious 1;
    }
    
    # 检查Accept-Language(大多数浏览器会发送)
    if ($http_accept_language = "") {
        set $suspicious 1;
    }
    
    # 检查Accept-Encoding(几乎所有浏览器都支持gzip)
    if ($http_accept_encoding !~* "gzip") {
        set $suspicious 1;
    }
    
    # 检查Connection头
    if ($http_connection !~* "keep-alive") {
        set $suspicious 1;
    }
    
    location / {
        if ($suspicious = 1) {
            # 可以返回444或重定向到验证页面
            return 444;
        }
    }
}

这个方法有一定误判风险,部分老旧的移动设备或特殊客户端可能不发送某些头。建议先在日志中记录可疑请求,观察一段时间确认无误判后再启用拦截。或者将可疑请求导向一个轻量级的验证页面,而不是直接拒绝。

日志监控与动态黑名单

被动防护总有极限,主动识别攻击源并动态封禁是更高级的做法。不需要安装fail2ban之类的工具,用一段简单的shell脚本配合crontab就能实现自动化封禁。

#!/bin/bash
# 分析Nginx访问日志,封禁高频IP
# 建议每分钟执行一次

LOG_FILE="/var/log/nginx/access.log"
BLOCK_LIST="/etc/nginx/blocked_ips.conf"
THRESHOLD=100  # 每分钟请求超过100次视为攻击

# 统计最近1分钟的请求次数
tail -n 10000 $LOG_FILE | awk -v d1="$(date -d '1 minute ago' '+%d/%b/%Y:%H:%M')" '
$0 ~ d1 {print $1}' | sort | uniq -c | sort -nr | 
while read count ip; do
    if [ $count -gt $THRESHOLD ]; then
        # 检查是否已经被封禁
        if ! grep -q "deny $ip;" $BLOCK_LIST 2>/dev/null; then
            echo "deny $ip;" >> $BLOCK_LIST
            echo "$(date) Blocked $ip - $count requests/min" >> /var/log/block_ip.log
        fi
    fi
done

# 重新加载Nginx(需要确认配置文件语法正确)
if [ -s $BLOCK_LIST ]; then
    nginx -t && nginx -s reload
fi

在Nginx主配置文件中引用这个黑名单文件:

http {
    # 在http块中引入黑名单
    include /etc/nginx/blocked_ips.conf;
    
    server {
        # 被封禁IP访问时返回403
        # deny指令会自动生效
    }
}

这个脚本的阈值需要根据实际流量调整。一个日活1000的网站和日活10万的网站,正常请求频率完全不同。建议先分析历史日志,取99分位的请求频率作为基准,阈值设置为基准的3-5倍。另外要加白名单机制,避免封禁搜索引擎爬虫和合作伙伴的IP。

多层防护的组合策略

单靠某一项技术无法应对复杂的CC攻击,需要将上述方法组合成多层防线。第一层是内核TCP参数优化,过滤掉最底层的SYN Flood和连接耗尽攻击。第二层是Nginx的limit_req和limit_conn,对动态页面做频率控制。第三层是请求头完整性检查和UA黑名单,拦截明显的脚本请求。第四层是日志分析和动态封禁,处理绕过前几层的漏网之鱼。

这种组合策略在实际测试中表现相当可靠。以一个2核4G的轻量云服务器为例,部署上述配置后,可以承受每秒约2000个CC攻击请求而不影响正常用户访问。攻击请求在第二层就被limit_req拦截返回503,正常用户因为burst设置合理,基本无感知。如果攻击者更换IP发起新一轮攻击,第四层的动态封禁会在1分钟内将其加入黑名单。

实际部署中的注意事项

CDN或反向代理后面的服务器需要特别注意获取真实IP。如果直接使用$binary_remote_addr,限制的将是CDN节点的IP而不是用户真实IP。需要配置real_ip模块来获取X-Forwarded-For或X-Real-IP头中的真实地址。

http {
    # 信任的CDN或代理IP段
    set_real_ip_from 10.0.0.0/8;
    set_real_ip_from 172.16.0.0/12;
    set_real_ip_from 192.168.0.0/16;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    
    # limit_req_zone使用真实IP
    limit_req_zone $binary_remote_addr zone=dynamic:10m rate=10r/s;
}

另外,503错误页面需要定制。默认的503页面会暴露Nginx版本信息,攻击者可以据此调整攻击策略。建议自定义一个简洁的503页面,不包含任何服务器信息。

server {
    error_page 503 /503.html;
    location = /503.html {
        internal;
        # 返回简单的文本提示
        return 503 "Service temporarily unavailable. Please try again later.";
    }
}

防护策略上线后要持续观察日志和监控指标。重点关注503响应的比例、正常用户的访问延迟、以及误封情况。根据实际数据不断调整rate和burst参数,找到防护效果和用户体验的最佳平衡点。这套无插件方案虽然比不上专业的高防服务,但对于日均攻击量在百万次以下的场景,已经足够应对绝大多数CC攻击。