服务器遭遇CC攻击怎么办?流量识别、限流与访问控制方案

早上九点,监控告警连续响起:网站首屏响应时间从平时的 200 毫秒涨到 8 秒以上,Nginx 的 active connections 从 300 左右飙到两万,CPU 使用率并不高但 load average 已经冲到 30,数据库连接池被占满,重启 PHP-FPM 后三分钟又打回原形。更奇怪的是带宽监控只涨了不到 20 Mbps,完全不像被"流量洪水"淹没的样子。这种"带宽不大、但服务器已经跪了"的现象,大概率就是遭遇了 CC 攻击(Challenge Collapsar)。它不像传统 DDoS 那样靠带宽压垮链路,而是用大量看似合法的 HTTP 请求去消耗应用层的连接、线程、数据库查询能力。本文只从防守和排查视角出发,讲清楚三件事:怎么在几分钟内判断是不是 CC 攻击、限流规则该怎么配才不误伤真实用户、访问控制应该在哪些层做。

一、先判断:这是 CC 攻击还是真实业务高峰

很多人一看到请求量暴涨就立刻上封 IP、上限流,结果把促销活动的真实用户全挡在门外,损失比攻击本身还大。动手之前,先用三个维度做流量识别:来源集中度、URI 集中度、连接状态分布。这三个指标在真实业务高峰和 CC 攻击下的表现差异非常明显。

1.1 看来源:IP 是否高度集中

真实用户的访问来源天然是分散的,Top 20 的 IP 通常只占总请求量的个位数百分比;而 CC 攻击由于依赖有限的僵尸主机或代理池,Top IP 的请求占比往往会高得离谱。用下面这条命令可以直接从 Nginx 访问日志里统计出排名靠前的来源 IP:

# 统计访问日志中 Top 20 来源 IP 及请求次数
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 统计每分钟请求量变化(按分钟聚合)
awk '{print $4}' /var/log/nginx/access.log | cut -d: -f2-3 | uniq -c | sort -rn | head -20

# 统计被请求最多的 URI
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

判读标准建议这样定:单个 IP 在 60 秒内的请求数超过 300 次,且这些请求集中在同一个或少数几个 URI 上,基本可以判定为异常;如果 Top 10 IP 的合计请求量占全站 40% 以上,而平时这个数字不到 5%,那么几乎可以确定是自动化流量。需要注意的是,如果来源 IP 分散在几千个地址上、每个 IP 每秒只有两三次请求,那更像是分布式低频 CC,此时按单 IP 限流效果有限,需要改用 URI 维度的限流和行为识别。

1.2 看连接:状态分布是否畸形

CC 攻击常见的两种形态是"高频新建连接"和"慢速连接保持"。前者会让你看到大量 SYN_RECV 和 TIME_WAIT,后者会让 ESTABLISHED 连接数长期维持在高位却几乎没有数据流动。用 ss 看整体统计和各状态分布:

# 查看连接总数与状态汇总
ss -s

# 按 TCP 状态分类计数
ss -ant | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

# 统计处于 SYN_RECV 状态的连接数
ss -ant state syn-recv | wc -l

# 查看某个 IP 建立了多少条连接
ss -ant | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

参考阈值:一台 4 核 8G 的常规 Web 服务器,ESTABLISHED 连接长期超过 8000、SYN_RECV 持续超过 500,都属于需要立刻介入的信号;如果 ss -s 里 TIME_WAIT 数量是 ESTABLISHED 的十倍以上,说明连接在极速创建又快速销毁,典型的短连接高频请求特征。同时不要忽略 fd 上限,ulimit -n 默认常见值是 1024,攻击期间很容易先撞上这个瓶颈,表现为日志里出现 "too many open files"。

1.3 一张表快速区分

观察维度CC 攻击特征真实业务高峰特征
带宽占用往往不高,几十 Mbps 以内随访问量线性增长,明显上升
Top IP 请求占比少数 IP 占比极高,可达 30%-60%分散,Top 10 通常低于 5%
URI 分布集中在少数需要消耗资源的接口(搜索、登录、下单)分布广泛,首页、列表、详情、静态资源都有
User-Agent高度重复,或缺失、或伪装成常见浏览器但行为异常多样化,包含大量真实浏览器标识
Referer大量为空或直接指向被攻击 URL 本身有正常站内跳转来源
时间分布突发式,几分钟内从基线跳到数十倍有爬升过程,与运营活动时间吻合
转化率请求量暴涨但订单/注册量不变甚至下降请求量与业务指标同步上涨

二、应急止血:前 15 分钟的标准动作

确认是异常流量后,不要急着改架构,先按下面这个顺序把损失降到最低。顺序很重要:先保住服务可用性,再精细化处理。

  1. 确认业务侧是否有活动:先和运营、市场确认当前是否有促销、投放、爬虫合作,避免误判。这一步只需要一分钟,但能避免后面所有的乌龙。
  2. 提高 fd 与连接上限:临时把 ulimit -n 提到 65535,并同步检查 Nginx 的 worker_rlimit_nofile 与 worker_connections,防止服务先因为资源上限崩掉。
  3. 先封最凶的几十个 IP:从 Top IP 列表里挑请求量异常且无业务意义的地址,用 iptables 直接丢弃,快速降低压力。
  4. 开启 Nginx 限流:按后文的 limit_req 配置加上速率限制,这一步是止血的主力。
  5. 临时降级非核心接口:搜索、排行榜、评论这类重查询接口可以先返回缓存或静态兜底页,把资源留给核心交易链路。
  6. 保留现场证据:在清理日志前把 access.log 与 ss -s 输出另存一份,方便事后复盘和向机房/运营商申诉。

需要提醒的是,单纯靠封 IP 很难根治 CC 攻击。攻击者更换 IP 的成本远低于你手工封禁的成本,所以封 IP 只能作为争取时间的手段,真正的主力必须是限流与访问控制策略。

三、Nginx 层限流:limit_req 与 limit_conn

Nginx 自带的两个模块是性价比最高的第一道防线:ngx_http_limit_req_module 限制请求速率,ngx_http_limit_conn_module 限制并发连接数。它们工作在应用层之前,开销极小,且不需要额外组件。

3.1 基础配置

# /etc/nginx/nginx.conf 的 http 段
http {
    # 按来源 IP 限请求速率,10MB 共享内存约可记录 16 万个 IPv4 状态
    limit_req_zone $binary_remote_addr zone=req_perip:10m rate=10r/s;

    # 按来源 IP 限并发连接数
    limit_conn_zone $binary_remote_addr zone=conn_perip:10m;

    # 对登录、搜索等重接口单独设一个更严格的区域
    limit_req_zone $binary_remote_addr zone=req_heavy:10m rate=2r/s;

    limit_req_status 429;
    limit_conn_status 429;
    limit_req_log_level warn;

    server {
        listen 80;
        server_name example.com;

        location / {
            limit_req zone=req_perip burst=20 nodelay;
            limit_conn conn_perip 20;
            proxy_pass http://backend;
        }

        location ~ ^/(login|search|api/order) {
            limit_req zone=req_heavy burst=5 nodelay;
            limit_conn conn_perip 5;
            proxy_pass http://backend;
        }

        # 静态资源放宽,避免误伤页面内的正常并发加载
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2)$ {
            limit_req zone=req_perip burst=60 nodelay;
            expires 7d;
            root /var/www/html;
        }
    }
}

3.2 参数怎么取值才不误伤

阈值不是拍脑袋定的,要基于你自己的基线。做法是先统计平时单 IP 的峰值请求速率,再乘以一个安全系数。举例来说,一个页面加载会产生 1 个文档请求加 15 到 30 个静态资源请求,如果静态资源已经走 CDN,那么单 IP 对动态接口的正常速率通常在 1 到 5 r/s 之间。因此 rate=10r/s 配 burst=20 对多数站点是相对安全的起点:正常用户几乎不会触发,而每秒上百次的脚本会立刻被拦截。

参数作用建议值取值过大/过小的后果
rate令牌生成速率,即允许的平均请求速率动态接口 5-10r/s,重接口 1-2r/s过小会误伤正常用户刷新;过大形同虚设
burst允许的突发队列长度rate 的 2-3 倍,建议 20-30过小会导致页面首次加载被拒
nodelay突发请求立即处理而不排队延迟建议开启不开则 burst 内请求会被排队延迟,用户体验差
zone 大小存储计数状态的共享内存10m(约 16 万 IPv4 状态)过小会报 "zone is out of memory" 并漏限
limit_conn单 IP 并发连接上限20-50过小会影响 HTTP/2 多路复用和浏览器并发
limit_req_status被限流时返回的状态码429(默认 503)用 503 容易被监控误判为服务故障

3.3 给内网和合作方留白名单

限流规则如果不做白名单,很容易把自己的监控探针、内网服务、支付回调打死。用 geo 和 map 组合可以实现"白名单 IP 不限流":

http {
    geo $limit {
        default        1;
        10.0.0.0/8     0;
        172.16.0.0/12  0;
        192.168.0.0/16 0;
        203.0.113.25   0;   # 合作方出口 IP
    }

    map $limit $limit_key {
        0 "";
        1 $binary_remote_addr;
    }

    limit_req_zone $limit_key zone=req_perip:10m rate=10r/s;
}

原理很简单:geo 把白名单网段的变量置为 0,map 再把值为 0 的情况映射成空字符串,而 Nginx 对空 key 的限流区域不做计数,等于放行了。改完记得用 nginx -t 校验配置,再用 systemctl reload nginx 平滑加载,不要用 restart,避免连接瞬间全断。

四、系统层访问控制:iptables / ufw / fail2ban

4.1 用 iptables 限制并发与新建速率

Nginx 限流挡的是已经进入应用的请求,而 iptables 可以在更底层直接丢弃,开销更低。两条最常用的规则是 connlimit(限制单 IP 并发连接数)和 recent(限制单位时间内的新建连接数)。

# 限制单个 IP 到 80 端口的并发连接数不超过 50
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 50 --connlimit-mask 32 -j REJECT --reject-with tcp-reset

# 限制单个 IP 60 秒内到 443 端口的新建连接不超过 100 个
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -m recent --set --name HTTPS
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -m recent --update --seconds 60 --hitcount 100 --name HTTPS -j DROP

# 查看规则命中计数,判断规则是否生效
iptables -L INPUT -n -v --line-numbers

如果你的系统是 Ubuntu 且使用 ufw,等价写法更简洁:ufw limit 22/tcp 会对 22 端口启用内置的速率限制(30 秒内超过 6 次连接即拒绝)。但要注意 ufw 的 limit 规则粒度较粗,做 Web 层细粒度限流仍建议直接用 iptables 或 nftables。

4.2 用 fail2ban 自动封禁反复触发限流的 IP

静态规则只能挡住固定阈值的流量,fail2ban 的价值在于"自动化的动态封禁":它扫描日志,对短时间内反复触发限流或认证失败的地址自动加入防火墙黑名单,并在 bantime 后自动解封,避免黑名单无限膨胀。

# /etc/fail2ban/jail.d/nginx-cc.conf
[nginx-limit-req]
enabled  = true
filter   = nginx-limit-req
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 10
findtime = 60
bantime  = 3600
action   = iptables-multiport[name=nginxcc, port="http,https", protocol=tcp]

[nginx-botsearch]
enabled  = true
filter   = nginx-botsearch
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 120
bantime  = 86400
# 使配置生效并观察状态
systemctl enable --now fail2ban
fail2ban-client reload nginx-limit-req
fail2ban-client status nginx-limit-req

# 查看当前被封 IP 列表
fail2ban-client get nginx-limit-req banip --with-time

# 误封时手动解封
fail2ban-client set nginx-limit-req unbanip 203.0.113.66

阈值建议:maxretry=10、findtime=60、bantime=3600 是一组对多数站点友好的初始值,意味着 60 秒内触发 10 次限流就封 1 小时。对于反复出现的顽固来源,可以用 bantime.increment 开启递增封禁:bantime = 3600 配合 bantime.increment = true、bantime.factor = 2、bantime.maxtime = 604800,让屡犯者封禁时长翻倍,最高一周。

五、架构层:为什么限流只是权宜之计

限流解决的是"不被打死",但解决不了"打不死"。当攻击流量超过单机出入口带宽,或者请求量大到 Nginx 本身都处理不过来时,本机限流已经无能为力。这时候需要把防线前移:

  • 接入 CDN 或高防:把源站 IP 隐藏起来,所有流量先经过边缘节点。这一步最关键的操作是更换源站 IP 并配置只允许 CDN 回源段访问,否则攻击者绕过 CDN 直打源站,前面做的全部白费。
  • 启用缓存与静态化:把首页、列表页、商品详情页静态化或加 Redis 缓存,让绝大多数请求在到达数据库之前就返回。一个命中率 90% 的页面缓存,等效于把后端抗压能力提升十倍。
  • 加人机校验:对登录、注册、评论、搜索这类高价值接口,在限流之后接一层 JS 挑战或无感验证码,成本远低于放行一次数据库查询。
  • 拆分读写与连接池:给 Web 层和数据库之间设置合理的连接池上限与超时时间,避免慢查询堆积把整库拖垮。建议给应用侧 SQL 设置 1 到 3 秒的硬超时。
  • 部署 WAF:云 WAF 或 ModSecurity 可以基于 UA、Header 完整性、请求频率、行为序列做更细的识别,适合处理"IP 分散但行为一致"的分布式 CC。

小结

面对 CC 攻击,正确的处理顺序是"识别—止血—限流—封禁—前移"。先用来源集中度、URI 集中度和连接状态三个指标确认这是攻击而不是业务高峰,再用提高 fd 上限、封禁 Top IP 争取时间,随后用 Nginx 的 limit_req 与 limit_conn 建立速率与并发双层限制,用 iptables 的 connlimit 与 recent 在系统层兜底,最后用 fail2ban 把人工封禁变成自动化闭环。

避坑建议

  • 不要一上来就封整段 IP:很多机房和家用宽带的出口 IP 是复用的,封 /24 段很容易误伤大量真实用户,尽量按 /32 单 IP 处理。
  • 不要忽略白名单:监控探针、支付回调、搜索引擎爬虫、内网服务必须先排除,否则会出现"攻击没挡住、业务先挂了"的尴尬局面。
  • 不要用 restart 加载 Nginx 配置:用 nginx -t 校验后 systemctl reload nginx,restart 会瞬断所有连接,等于帮攻击者完成一次服务中断。
  • 不要只看 CPU:CC 攻击期间 CPU 往往不高,瓶颈在连接数、fd 数量、数据库线程池,监控指标要覆盖这些维度。
  • 不要忘了隐藏源站 IP:上了 CDN 却没换源站 IP,是新手最常踩的坑,攻击者只要知道真实 IP,一切边缘防护都能绕过。
  • 阈值要留余量并持续观察:限流规则上线后,务必持续观察 429 的数量和被限 IP 的特征,攻击结束或业务量变化后及时调整,长期不调整的限流规则迟早会误伤真实用户。