个人站点遭遇CC攻击时,最直观的感受不是带宽跑满,而是服务器CPU瞬间飙升,数据库连接数耗尽,网站无法打开。CC攻击本质上模拟了真实用户的访问行为,它不像SYN Flood那样可以用简单的内核参数调优去硬扛,它攻击的是应用层,是动态页面、搜索接口、数据库查询这些最消耗资源的环节。因此,防护的核心思路不是“封IP”,而是“把有限的服务器资源留给真实用户”。
精准识别:找出哪些URL在被恶意刷动手配置之前,必须先把攻击目标找出来。攻击者通常不会漫无目的地刷首页,因为静态首页消耗资源少,打不出效果。他们往往会瞄准搜索框、注册页面、密码找回、带有复杂数据库查询的列表页,甚至是那些会触发写操作的API接口。登录服务器后,第一时间查看Web服务器的访问日志,重点分析请求频率最高的URL和User-Agent。
以Nginx为例,使用awk命令快速统计访问最频繁的页面:
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
这条命令会列出最近被访问最多的20个URL。如果看到某个搜索接口或者动态页面每秒请求量远超正常值,基本可以确定这就是攻击目标。同时,也要统计访问最频繁的IP地址:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
CC攻击的IP分布有两种典型特征:一种是单个或几个IP发起极高频率请求,这种用封IP就能解决;另一种是大量分布式代理IP,每个IP只发几十个请求,但总量巨大,这种单纯封IP会误伤,需要更精细的规则。观察User-Agent字段也很关键,很多攻击脚本使用默认的Python-requests、Go-http-client等特征头,或者干脆是空值。
Nginx核心防护:limit_req和limit_conn的实战调优Nginx自带的ngx_http_limit_req_module和ngx_http_limit_conn_module是抵御CC攻击的第一道防线,但大部分教程只教怎么开启,不教参数怎么调。这两个模块的精髓在于burst和nodelay的组合使用。
limit_req_zone用来定义共享内存区域和限流key。通常基于客户端IP来限制,但针对CC攻击,更推荐使用IP加URL的组合作为key,这样可以精确限制对某个特定接口的访问频率,而不影响用户正常浏览其他页面。在nginx.conf的http块中添加:
limit_req_zone $binary_remote_addr$uri zone=per_uri:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=per_ip:10m rate=20r/s;
第一条规则针对每个IP对每个URL的请求限制为每秒5次,第二条规则针对每个IP的总请求限制为每秒20次。这样即使攻击者换着URL刷,总频率也会被第二条规则兜底拦截。在server或location块中应用规则时,burst参数是关键:
location /search {
limit_req zone=per_uri burst=10 nodelay;
proxy_pass http://backend;
}
burst=10意味着允许在限速基础上突发10个请求,nodelay表示这些突发请求立即处理而不延迟。如果没有nodelay,突发请求会被排队延迟处理,对于CC攻击来说延迟处理反而会堆积连接,耗尽worker连接数。实际配置时,rate不要设得太低,否则正常用户稍微连续点击就会被拦截。建议先通过日志分析正常用户的访问频率峰值,取峰值的1.5到2倍作为rate值,burst设置为rate的2到3倍。
limit_conn用来限制并发连接数,这个对CC攻击同样有效。攻击者如果保持大量长连接占用服务器资源,即使请求频率不高也会造成服务不可用:
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m; limit_conn conn_per_ip 10;
这个配置限制每个IP同时最多保持10个连接。注意limit_conn要放在server块中,并且要配合keepalive_timeout调小使用,否则正常浏览器会复用连接,可能误触限制。建议把keepalive_timeout从默认的65秒降到15到30秒,让连接快速释放。
动态挑战:用Nginx+Lua实现Cookie验证对于分布式的低速CC攻击,单纯靠频率限制很难区分攻击者和正常用户。这时候需要引入动态挑战机制,原理是:当某个IP的请求频率超过阈值时,不直接拒绝,而是返回一个302重定向,要求客户端设置一个特定的Cookie。正常的浏览器会自动处理重定向并携带Cookie,而大部分攻击脚本不会解析和执行重定向。
这个功能需要Nginx安装lua-nginx-module模块。在nginx.conf中配置:
location / {
access_by_lua_block {
local cookie = ngx.var.cookie_cc_defense
if not cookie then
local ip = ngx.var.binary_remote_addr
local limit = ngx.shared.limit_req:get(ip)
if limit and limit > 20 then
ngx.header['Set-Cookie'] = 'cc_defense=1; path=/; max-age=3600'
return ngx.redirect('https://' .. ngx.var.host .. ngx.var.request_uri, 302)
end
end
}
proxy_pass http://backend;
}
这段逻辑是:检查请求是否携带cc_defense这个Cookie,如果没有,就去共享内存中查询该IP的请求计数,如果超过20次就返回302重定向并设置Cookie。浏览器会自动带着Cookie重新请求,而攻击脚本通常到此为止。共享内存的计数需要配合limit_req模块来维护,或者单独用Lua写一个计数器。这个方案比直接封IP优雅得多,对搜索引擎爬虫也友好,因为正规爬虫同样会处理Cookie。
WAF规则:ModSecurity的CC专用规则集如果服务器上装了ModSecurity,可以直接用它的规则引擎来检测CC攻击。ModSecurity的优势在于可以结合请求特征、会话状态、全局计数器做综合判断,而不是孤立的单IP频率判断。在modsecurity.conf中添加自定义规则:
SecRule REQUEST_URI "@contains /search" \
"id:10001,phase:2,chain,t:none,deny,status:429,msg:'CC Attack Detected'"
SecRule IP:REQUEST_FREQUENCY "@gt 30" \
"id:10002,t:none,setvar:ip.cc_counter=+1,expirevar:ip.cc_counter=60"
这条规则监控/search接口,当单个IP在60秒内请求超过30次时,返回429状态码。expirevar参数让计数器60秒后自动过期,避免内存无限增长。更高级的用法是结合SecAction和ipMatchFromFile,将频繁触发规则的IP自动加入黑名单文件,实现动态封禁。
系统层加固:内核参数和iptables的协同防御Web应用层的防护做好之后,系统层也要做相应调整。CC攻击会产生大量的TIME_WAIT和ESTABLISHED连接,默认的内核参数可能撑不住。编辑/etc/sysctl.conf:
net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 8192 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_max_tw_buckets = 5000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15 net.ipv4.ip_local_port_range = 1024 65000
tcp_syncookies开启SYN Cookie防护,虽然CC主要是应用层攻击,但攻击流量中往往混杂SYN Flood,开启这个不会影响正常业务。tcp_max_tw_buckets限制TIME_WAIT状态的最大数量,防止连接表被占满。tcp_fin_timeout调小到15秒,让连接快速回收。这些参数调整后执行sysctl -p生效。
iptables也可以作为补充手段,对单个IP的并发连接数做限制:
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 30 --connlimit-mask 32 -j DROP iptables -A INPUT -p tcp --dport 443 -m connlimit --connlimit-above 30 --connlimit-mask 32 -j DROP
这两条规则限制每个IP对80和443端口的并发连接数不超过30个。注意这个数字要和Nginx的limit_conn保持一致或略大,否则iptables先于Nginx丢包,会导致Nginx的日志记录不到被拦截的请求,不利于后续分析。
CDN层面的前置过滤如果个人站点已经接入了CDN,一定要利用好CDN提供的CC防护功能。大多数CDN服务商都提供基础CC防护,在控制台开启后,CDN边缘节点会先过滤掉明显的攻击流量,回源请求会干净很多。配置时注意开启“强制跳转HTTPS”和“浏览器完整性检查”,这两个选项能过滤掉大量脚本攻击。
CDN的速率限制功能要配合源站策略使用。比如CDN设置单IP每秒10次请求限制,源站的Nginx可以适当放宽到每秒20次,因为到达源站的已经是经过CDN过滤后的请求。同时,务必在Nginx中配置只信任CDN的回源IP,防止攻击者绕过CDN直接攻击源站:
location / {
allow CDN_IP_RANGE_1;
allow CDN_IP_RANGE_2;
deny all;
proxy_pass http://backend;
}
CDN的回源IP段可以从服务商文档获取,定期更新。这个配置配合CDN的“源站保护”功能使用,能有效防止源站IP泄露后被直接攻击。
日志监控与自动化响应防护配置部署之后,持续的监控和自动化调整同样重要。写一个简单的Shell脚本,每分钟检查一次访问日志,当某个IP的请求量超过阈值时,自动添加到iptables临时黑名单,并设置过期时间:
#!/bin/bash
tail -n 5000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | while read count ip; do
if [ $count -gt 100 ]; then
iptables -A INPUT -s $ip -j DROP
echo "$(date) Blocked $ip with $count requests" >> /var/log/cc_block.log
(sleep 3600 && iptables -D INPUT -s $ip -j DROP) &
fi
done
这个脚本每分析最近5000条日志,对单分钟请求超过100次的IP执行封禁,一小时后自动解封。把脚本加入crontab每分钟执行一次,就能实现基础的自动化防御。更完善的做法是把被封IP记录到持久化存储中,避免重启后丢失,同时设置白名单确保自己的IP和搜索引擎爬虫IP不会被误封。
个人站点的CC防护没有一劳永逸的方案,攻击手法在变,防护策略也要跟着迭代。核心原则始终是:分层防御、精准识别、动态响应、资源保护。把Nginx的限流、Lua的挑战验证、系统内核调优、CDN前置过滤组合起来,才能用最小的成本扛住绝大多数CC攻击。
