服务器遭遇CC攻击怎么办?访问日志分析与限流防护策略

网站突然变得很慢,甚至打不开,但去看服务器资源——CPU 不算高、内存还有富余、带宽也没跑满。这种情况常常不是什么"服务器配置不够",而是遭遇了 CC 攻击:攻击者用大量看起来完全正常的请求,把你的应用层资源(数据库连接、后端进程、PHP-FPM 工作进程)占满,让你自己的正常用户进不来。它不像传统的带宽型攻击那样把链路堵死,而是"点到为止"地用合法请求拖垮你的应用,所以特别隐蔽。

这篇文章从防御和运维的角度,讲清楚 CC 攻击的特征怎么识别、怎么用 Nginx 的访问日志做分析(Top IP、Top URL、Top UA 统计)、以及怎么用限流、超时、WAF、CDN 分流、缓存静态化、验证码这一整套手段做防护。重点是"防守",让你看懂自己的日志、把正常流量和异常流量区分开、把资源留给真正的用户。

需要明确一个边界:本文只讲防御与运维视角的分析和防护配置,不介绍任何发起攻击的方法、工具或参数。任何"打别人"的内容都不在讨论范围内。我们关心的是:当流量异常时,如何识别、如何缓解、如何加固。

一、CC 攻击的特征:为什么它难缠

先搞清楚 CC 攻击和带宽型攻击(DDoS)的区别。带宽型攻击的目标是把网络带宽打满,表现为流量图暴涨、链路拥塞,特征明显。CC 攻击的目标是耗尽"应用层资源"——每一次请求本身都很小,但处理的代价高(要查数据库、要渲染页面、要占用 PHP 进程),累积起来就把应用拖垮。所以它的特征不在带宽图上,而在应用指标上。

CC 攻击的典型特征有这么几条。第一,请求量大但单个请求很小,通常是频繁访问那些"动态、耗资源"的接口,比如搜索、登录、列表分页、动态页面。第二,来源 IP 相对集中或者 UA 高度一致。早期的简单 CC 用几个 IP 猛打,容易被封;现在常见的是用一批 IP 或代理资源,但 UA、URL 往往印着同一批特征。第三,访问的 URL 高度集中,异常流量会反复打同一个或同一类接口。第四,QPS(每秒请求数)异常,正常业务有波峰波谷,攻击流量往往是一条持续的高位直线,或者在某个时间点突然抬高。第五,请求的成功率异常,很多请求返回 200 但其实是在空跑动态逻辑,或者返回 302 跳转/401,形成"无效但耗资源"的循环。

和正常促销、活动带来的流量高峰对比,CC 攻击的区别在于:正常高峰通常是分散的真实用户,UA 多样、URL 分布广、有正常的点击行为和会话;而攻击流量往往背离这一规律,集中在少数维度上。识别的关键,就是从日志里把这些"不自然"的规律找出来。

二、先用日志看清发生了什么

Nginx 的访问日志是排查 CC 攻击的核心数据源。默认的 access.log 每行是一条访问记录,格式大致是:客户端 IP、时间、请求方法、URL、状态码、响应大小、Referer、User-Agent。这些字段恰好覆盖了识别 CC 攻击需要的所有维度。

先确认日志路径和格式。常见位置是 /var/log/nginx/access.log,格式由 nginx.conf 里的 log_format 定义。

# 定位访问日志
ls -l /var/log/nginx/access.log*

# 看日志格式定义(确认各字段位置)
sudo nginx -T 2>/dev/null | grep -A2 "log_format"

搞清楚字段位置很重要,因为后面所有统计命令都依赖它。默认的 combined 格式里,第一个字段是客户端 IP,第 7 个是 URL,最后的引号部分是 UA。

接下来做几个关键统计。

# 1) 访问量最高的来源 IP Top 20(找异常集中的来源)
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 2) 被访问最多的 URL Top 20(找被反复打的接口)
sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 3) User-Agent Top 20(找 UA 高度一致的可疑流量)
sudo awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 4) 每秒请求数(QPS)趋势:按秒统计,找出异常时段
sudo awk '{print $4}' /var/log/nginx/access.log | cut -d: -f1-3 | uniq -c | sort -rn | head -20

# 5) 状态码分布(看是不是大量 200/302/401/499)
sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

第一条和第二条命令最直接:如果某个 IP 的请求数远超其他,或者某个 URL 被打了占总量很大比例的次数,就高度可疑。第三条帮你发现"UA 全都一样"的批量请求。第四条按秒聚合,能看出 QPS 是否有突然拉高的时段。第五条的状态码分布里,499 值得特别关注——它表示客户端在服务器响应前就断开了连接,大量 499 往往是"请求发出后不等响应就走"的批量行为特征之一。

如果想看某个可疑 IP 到底在访问什么,可以把它单独拉出来:

# 查看某个可疑 IP 的访问明细
sudo grep "203.0.113.99" /var/log/nginx/access.log | tail -50

# 统计该 IP 访问的 URL 分布
sudo grep "203.0.113.99" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

# 统计该 IP 在每分钟的请求数
sudo grep "203.0.113.99" /var/log/nginx/access.log | awk '{print $4}' | cut -d: -f1-3 | uniq -c

这几条命令合起来,基本能给你一幅清晰的画面:谁在打、打什么、多快、用什么身份。有了这幅画面,后面的防护才有针对性。

如果日志量大、或者日志已经轮转,用 zcat/zgrep 查压缩的历史日志,或者把日志接入集中式分析平台(如 ELK、Loki、日志服务)做可视化,长期看更省力。

# 查压缩的历史日志
sudo zgrep -c "" /var/log/nginx/access.log.*.gz 2>/dev/null | tail
sudo zcat /var/log/nginx/access.log.*.gz | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

三、第一层防护:Nginx 限流

Nginx 自带两个限流模块,是应用层防护的第一道闸门,配置简单、效果直接:limit_req 限制请求速率,limit_conn 限制并发连接数。

limit_req 基于"漏桶"算法,用两个参数定义:rate 是允许的平均速率,burst 是允许的突发额度。超过 rate 但没超过 burst 的请求会被延迟处理(或按要求直接拒绝),超过 burst 的直接返回 503。

# 在 http 块中定义限流区
# $binary_remote_addr 以客户端 IP 为键,zone 是共享内存区名和大小,rate 是每秒允许请求数
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=5r/s;

server {
    listen 80;
    server_name example.com;

    # 对全站生效:平均 5 请求/秒,允许突发 20 个,超出按速率延后处理
    location / {
        limit_req zone=req_per_ip burst=20 nodelay;
        proxy_pass http://backend;
    }

    # 对耗资源接口收紧:比如搜索、登录,速率更低
    location /search {
        limit_req zone=req_per_ip burst=5 nodelay;
        proxy_pass http://backend;
    }
}

nodelay 的含义要理解:不加它,超出的请求会被排队慢慢处理,客户端会感觉变慢;加上它,突发额度内的请求立即处理、超出额度的立即拒绝(返回 503)。对"防刷"场景,通常加上 nodelay,宁可快速拒绝,也不让请求堆积拖垮后端。

limit_conn 用来限制同一个 IP 同时保持的连接数,防止一个来源开大量并发连接占满后端。它常用于下载、大文件、或者长连接场景。

# 定义并发连接区
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

server {
    # 同一 IP 最多同时 10 个连接
    limit_conn conn_per_ip 10;
    # 连接数超限时返回的状态码
    limit_conn_status 429;
}

限流还有一个容易被忽视的配套:limit_req_status 和错误页。默认超限返回 503,看起来像服务器出错,其实是被限流。可以改成 429,语义更准确,也方便和真实故障区分。

limit_req_status 429;

风险提示:限流阈值设得太低会误伤正常用户,尤其是 NAT 后的办公网络(很多人共用一个出口 IP)或者搜索引擎的抓取。上线前先观察正常业务的 QPS 分布,把阈值设在"正常峰值之上、异常值之下"。建议先配置成只记录不拦截(用 limit_req_log_level 观察日志)来评估影响,再正式启用。回滚方式:注释掉 limit_req/limit_conn 指令并 nginx -s reload。

四、超时与连接控制:让慢连接不能拖垮你

CC 攻击经常利用"慢"来占资源——连接建立后慢慢发数据、或者请求发出后不积极接收响应,让服务器的连接和进程长时间被占住。收紧各类超时,能让这类占资源的连接尽快释放。

# 客户端请求体读取超时
client_body_timeout 10s;
# 客户端请求头读取超时
client_header_timeout 10s;
# 向客户端发送响应的超时
send_timeout 10s;
# 允许客户端发送请求体的最大时间
client_max_body_size 10m;

# keepalive 连接数限制(防止长期占用的空闲连接堆叠)
keepalive_requests 100;
keepalive_timeout 30s;

超时的思路是"不给慢连接太多时间"。正常的请求在毫秒到几百毫秒内就该完成,10 秒的超时对正常用户完全够用,但对刻意拖时间的连接就是一道限制。注意不要把超时设得过短,否则在移动网络或弱网环境下可能误伤正常用户——10 到 30 秒是常见的经验区间。

另外,限制单 IP 的请求体大小、禁用不必要的 HTTP 方法,也能减少可被滥用的面。

# 只允许常用方法(示例,按业务调整)
if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 405;
}

五、缓存静态化与 CDN 分流:把压力甩出去

限流是"节流",缓存是"开源"——把耗资源的动态请求变成廉价的静态响应,从根本上降低后端压力。CC 攻击打的是动态接口,如果你的页面能被缓存,大量重复请求就不会每次都打到后端。

# 对静态资源做长期缓存
location ~* \.(jpg|jpeg|png|gif|css|js|ico|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000";
    access_log off;
}

# 对可缓存的动态页面用 proxy_cache 缓存(示例)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=page_cache:10m max_size=1g inactive=60m;

location / {
    proxy_cache page_cache;
    proxy_cache_valid 200 302 10m;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    proxy_pass http://backend;
}

用缓存对付 CC 攻击的关键前提是:被打的接口"对同一请求能返回相同结果"。如果接口本身依赖用户会话(比如登录后的个性化页面),就不能简单缓存。这时候更有效的做法是把压力转移到 CDN。

CDN 分流是应对 CC 攻击最省心的手段之一:静态资源、图片、视频交给 CDN 带掉;有条件的还能把整个站点接入 CDN,让 CDN 层承担一部分流量清洗和缓存,源站只接受回源请求。接入 CDN 的好处是,源站不再直接面对攻击流量,同时 CDN 通常自带一定的防护能力。代价是要处理回源 IP 白名单、缓存刷新、以及接入后的排障(这些在其他文章里有详细讲)。

无论用缓存还是 CDN,核心思路是一样的:让尽可能多的请求在到达后端之前就被响应掉。后端能处理的请求少了,被拖垮的概率就低了。

六、WAF 与验证码:识别"不像人"的流量

限流和缓存处理的是"量",WAF 和验证码处理的是"质"——识别出那些看起来是请求、但不是真人操作的流量。

WAF(Web 应用防火墙)可以在应用层按规则匹配请求特征:异常的 UA、可疑的请求频率、已知的攻击特征、不合理的参数等。它可以是云厂商提供的 WAF 服务,也可以是自建的开源方案。CC 攻击场景下,WAF 的价值在于"集中管理规则"和"按特征动态拦截",你不需要自己写一堆 Nginx 规则。

验证码是区分人和脚本的经典手段。在耗资源的入口(登录、搜索、提交表单)加上验证码,能有效拦截那些批量自动请求——因为对应的自动化请求通常无法通过验证码。但验证码有代价:它会增加正常用户的操作步骤,用多了伤体验。所以它更适合放在"高风险且低频"的入口上,而不是全站。

一个务实的组合是:对全站做限流 + 缓存,对耗资源接口收紧限流并加 WAF 规则,对高风险入口加验证码。这三层配合,既不误伤太多正常用户,又能挡住大部分批量请求。

关于"封 IP 段"这件事,需要泼一盆冷水:直接封整个 IP 段(尤其是一整个运营商段或云厂商段)风险很高。第一,攻击者用什么 IP 段是变化的,封了旧的又要封新的,疲于奔命;第二,被误封的网段里可能有大量正常用户(比如某个云厂商的出口、某个运营商的大段),一封就会伤及无辜。所以封 IP 段只能作为临时应急,且范围要尽量小、时间要尽量短,长期还是要靠"限流 + 缓存 + 行为识别"这套更细的手段。

# 临时应急:在应急场景下临时封禁一个明确的高频来源 IP(谨慎使用,记得撤销)
# RHEL 系:sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.99" drop'
# Debian/Ubuntu:sudo ufw deny from 203.0.113.99
# 应急结束后及时移除,避免误伤或规则堆积

风险提示:临时封 IP 是应急手段,务必设定明确的撤销时间点,并在事后复盘是否有误伤。对于来源 IP 频繁变化的攻击,封 IP 收效有限,不应作为主要策略。

七、一套可落地的处置顺序

真遇到 CC 攻击时,按下面的顺序处理,先止血再根治。

第一步,确认是不是 CC 攻击。看日志统计(Top IP、Top URL、UA、QPS),确认是不是"少量维度异常集中、后端资源被占满、但带宽没满"。排除掉自身代码 Bug、促销活动、爬虫等正常原因。

第二步,先止血。如果已经影响业务,优先做能立刻见效的动作:临时收紧耗资源接口的限流阈值、必要时对确凿的高频来源做临时封禁、临时关闭非必要的动态功能。目标是让正常用户能进得来。

第三步,加强缓存和 CDN。把可缓存的请求甩给缓存或 CDN,从源头降低后端压力。这一步往往比封 IP 更有效,也更可持续。

第四步,上行为识别。用 WAF 规则拦截特征明确的异常请求,对高风险入口加验证码。这一步是精细化的过滤。

第五步,复盘和固化。攻击过去之后,把这次有效的配置沉淀下来,纳入日常防护;优化架构(比如引入 CDN、把动态页面尽量静态化、加固数据库连接池),提升长期抗压能力。

常见问题

Q:怎么区分是 CC 攻击还是正常流量高峰?
看分布的"自然度"。正常高峰的 UA、URL、来源 IP 都多样,有真实用户的行为特征;CC 攻击往往在少数维度上高度集中(同一批 UA、反复打同一接口、QPS 呈现异常形状)。结合后端的错误率和响应时间一起判断,比单看流量更准。

Q:限流会不会误伤正常用户?
有可能,尤其是阈值设得过低时。对策是先观察正常业务的请求分布,把阈值设在正常峰值之上;对 NAT 后共用出口的场景适当放宽;先记录日志评估影响,再正式拦截。宁可稍微宽松一点,也不要为了防攻击把正常用户全挡了。

Q:被打了要不要直接封 IP?
短期应急可以,但要谨慎。封单一确凿的高频 IP 问题不大;封 IP 段则风险很高,容易误伤正常用户,而且攻击者换段就失效。长期来看,限流、缓存、CDN、行为识别这套组合远比封 IP 有效。

Q:接入 CDN 之后为什么还是被打?
CDN 能分担流量,但要正确配置才有防护效果:源站要对 CDN 回源 IP 做白名单,避免攻击者绕过 CDN 直连源站;同时启用 CDN 自身的防护能力。如果源站 IP 还能被直接访问,攻击流量会绕过 CDN,效果就打折扣。

Q:验证码加在哪里合适?
放在"耗资源且不常发生"的入口,比如登录、注册、搜索、提交订单。这些入口天然低频,加验证码对体验影响小,但能有效拦住批量自动请求。不要全站加,那样会伤正常用户的体验。

总结

CC 攻击的特征是"用合法请求拖垮应用",它不体现在带宽上,而体现在后端的请求压力、连接占用和响应时间上。识别它,要看请求的集中度、QPS 形状和状态码分布,而不是只看流量图。

Nginx 访问日志是排查的核心数据源。用 awk+sort+uniq 统计 Top IP、Top URL、Top UA 和每秒请求数,几分钟就能看清"谁在打、打什么、多快"。这些统计是后续所有防护的依据。

防护是一个组合,而不是单一手段:限流(limit_req/limit_conn)负责节流,超时收紧负责释放被占的连接,缓存和 CDN 负责把压力甩出去,WAF 和验证码负责识别"不像人"的流量。分开用各有短板,组合起来才有效。

封 IP 是应急手段,不是长期策略。攻击者换 IP 就绕过,而且封大段容易误伤。更可持续的思路是让后端"扛得住":缓存静态化、CDN 分流、把动态请求尽量变成可缓存的响应。

最后,把这次有效的配置沉淀下来,纳入日常防护和监控。抗 CC 的能力不是一次性配好的,而是在每次流量考验中逐步加固出来的。