初创企业站点遭遇CC攻击,最直观的感受不是带宽被占满,而是数据库连接数瞬间飙升,CPU直接100%,网站无法打开。本质上,CC攻击模拟的是大量正常用户的访问行为,它不像SYN Flood那样可以用简单的网络层规则过滤,因为攻击请求在HTTP层面看起来完全合法。对于预算有限的初创团队,买不起高防IP,也养不起全天候的安全运维,但并不意味着只能躺平挨打。核心思路是把防御前置,用极低甚至零成本的手段,在请求到达服务器应用层之前就完成清洗。

利用Nginx的请求限制模块做第一层削峰

绝大多数初创站点都运行在Nginx或类似的反向代理后面,这是成本最低、见效最快的防御阵地。很多人知道limit_req模块,但配置往往过于保守,没有发挥真正价值。你需要理解两个参数:rate和burst。rate定义的是稳定速率,比如每秒允许10个请求;burst定义的是允许突发的队列长度。关键点在于,burst不是无代价的,如果设置nodelay,队列中的请求会立即处理,这可能导致后端瞬间压力过大。对于CC防御,推荐的做法是不使用nodelay,让超过速率的请求排队等待,攻击请求会在队列中耗尽超时时间而被丢弃,而正常用户即便遇到短暂排队,只要burst值设置合理,页面加载的延迟在200-300毫秒内用户几乎无感知。

具体配置示例如下:

http {
    limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
    limit_req_zone $server_name zone=perserver:10m rate=100r/s;

    server {
        location / {
            limit_req zone=perip burst=20 nodelay;
            limit_req zone=perserver burst=200;
            proxy_pass http://backend;
        }
    }
}

这里做了两层限制:单IP每秒10个请求,突发队列20个,且不延迟处理;服务器整体每秒100个请求,突发队列200个。单IP限制拦截的是单个攻击源的高频请求,服务器整体限制防止多IP分布式攻击打垮后端。10m的内存区域可以存储约16万个IP的状态,对于初创站点完全够用。如果攻击流量更大,可以适当调低rate值,但要注意不要误伤正常用户,建议通过分析访问日志中正常用户的请求频率来确定合理阈值。

用浏览器验证机制区分真人和脚本

CC攻击工具虽然能模拟HTTP请求,但绝大多数无法执行JavaScript代码,这是区分真人和攻击脚本的天然分界线。实现方式不是简单地在页面里插入一段JS,而是要在反向代理层做无感验证。具体做法是:当某个IP的请求频率超过阈值,Nginx通过error_page指令将请求重定向到一个验证页面,这个页面包含一小段JS代码,执行后会设置一个加密的Cookie。后续请求携带这个Cookie才能正常访问后端,否则持续返回验证页面。

实现这个逻辑不需要引入第三方服务,Nginx配合OpenResty的Lua模块就能完成。以下是一个简化的实现思路:

location / {
    access_by_lua_block {
        local cookie = ngx.var.cookie_cc_verify
        if not cookie or not verify_token(cookie) then
            if ngx.var.request_uri ~= "/verify.html" then
                return ngx.redirect("/verify.html")
            end
        end
    }
    proxy_pass http://backend;
}

location = /verify.html {
    root /var/www/verify;
}

verify.html页面中的JS脚本会执行一个简单的计算任务,比如生成一个时间戳加随机数的哈希值,然后写入Cookie。攻击脚本通常不会解析和执行JS,也就无法获得有效Cookie,从而被挡在门外。这个方案的精妙之处在于,它对正常用户几乎无感——首次访问可能有一个极短的重定向,之后浏览器自动携带Cookie,后续访问完全不受影响。而攻击流量会被大量消耗在验证环节,攻击成本急剧上升。

利用CDN的免费额度做边缘清洗

国内主流CDN服务商都提供一定额度的免费流量和请求次数,对于初创站点,这个免费额度往往能覆盖日常流量。但关键在于,你需要正确配置CDN的防护策略,而不是简单地做加速。CDN节点分布在各地,天然具备分散攻击流量的能力。你需要开启的基础防护包括:IP访问频率限制,这个在CDN控制台通常可以直接设置,阈值建议比Nginx层的设置稍宽松,因为CDN层拦截的是最明显的异常流量;UA黑白名单,很多CC攻击工具使用默认的库发起请求,UA特征明显,比如Python-urllib、Go-http-client等,直接封禁这些非浏览器UA不会影响正常用户。

更深度的用法是配置CDN的缓存策略来减轻源站压力。CC攻击往往针对动态页面,因为动态页面消耗资源最大。你可以将那些实际上不需要实时生成的内容,比如商品列表、文章详情等,在CDN侧设置较短的缓存时间,比如30秒或1分钟。这样即便有大量请求,CDN边缘节点直接返回缓存内容,请求根本不会回源。对于确实需要实时数据的接口,可以在应用层做一层内存缓存,比如使用Redis缓存查询结果,将数据库查询转换为内存读取,大幅提升抗压能力。

应用层的轻量级防御策略

当请求已经到达应用服务器,防御手段必须极其高效,任何额外的处理开销都可能成为压垮服务器的最后一根稻草。在应用层,最值得投入的是请求排队和异步处理机制。如果你的应用是PHP,可以考虑在Nginx和PHP-FPM之间引入一个基于共享内存的请求计数器。PHP脚本执行前先检查当前正在处理的请求数,超过阈值直接返回503,让Nginx去处理重试逻辑。这样做的好处是,PHP-FPM进程池不会被耗尽,始终有资源处理正常请求。

对于数据库层面的防护,读写分离是基础操作,但针对CC攻击还需要更细致的优化。攻击请求往往携带大量参数,导致SQL查询不走索引,产生大量慢查询。你可以在数据库连接层设置查询超时时间,比如MySQL的max_execution_time参数,强制终止执行时间过长的查询。同时,在应用代码中,对每个接口的查询复杂度做评估,给高风险接口加上请求速率限制,这个限制可以基于Redis实现一个简单的滑动窗口计数器。

日志监控与自动化响应

防御不是一次配置就完事,攻击者会不断调整策略。你需要一套轻量级的监控和自动化响应机制。Nginx的访问日志本身就包含大量信息,通过定时分析日志,可以及时发现异常模式。一个实用的做法是写一个Shell脚本,每分钟分析一次最近的访问日志,统计每个IP的请求数、请求的URL分布、状态码分布。如果某个IP在短时间内产生了大量404或502状态码,或者集中请求同一个URL,就自动将其加入iptables或Nginx的封禁列表。

以下是一个简单的自动封禁脚本示例:

#!/bin/bash
tail -n 10000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | while read count ip; do
    if [ $count -gt 200 ]; then
        iptables -I INPUT -s $ip -j DROP
        echo "$(date) blocked $ip with $count requests" >> /var/log/cc_block.log
    fi
done

这个脚本每执行一次,分析最近一万条日志,如果某个IP出现超过200次,就直接在iptables层面封禁。200这个阈值需要根据你的实际流量调整,一般设置为正常用户峰值请求量的5到10倍比较合理。将脚本加入crontab每分钟执行,就能实现准实时的自动封禁。更进阶的做法是结合Redis存储封禁列表,设置自动过期时间,避免永久封禁误伤动态IP的正常用户。

构建多层防御体系的整体架构

把上述所有手段组合起来,形成一条完整的防御链:CDN边缘节点是第一层,拦截最明显的恶意流量,缓存静态和半静态内容;Nginx反向代理是第二层,执行IP频率限制和浏览器验证,把脚本攻击挡在应用之外;应用服务器是第三层,通过请求队列和快速失败机制保护数据库;数据库层是最后一道防线,通过查询超时和索引优化保证核心数据操作不被拖垮。每一层都有明确的职责,每一层都能独立拦截一部分攻击,即使某一层被突破,后续层级仍有防御能力。

这个架构的总成本是多少?CDN使用免费额度,Nginx是开源软件,Redis可以使用单实例免费版,Shell脚本自己编写。唯一的成本是配置和维护的时间投入,而这正是初创团队最应该投入的地方——用技术能力换取资金成本的降低。这套方案经过实际验证,能够有效抵御每秒数千次请求的CC攻击,对于绝大多数初创企业站点来说,防护能力已经绰绰有余。当业务增长到需要更高等级的防护时,这套架构也可以平滑升级,比如在CDN层接入付费的WAF服务,或者将Nginx替换为商业版的高防产品,底层的防御逻辑和架构思路依然适用。