先讲一件我亲身经历的事。凌晨两点多值班手机震个不停告警只有一句话Web服务平均响应时间从80毫秒直接飙到15秒以上。我爬起来打开监控大屏第一眼去看带宽曲线——平坦得像一条直线流量型DDoS的特征完全没有。再切到CPU和数据库监控CPU已经烧到95%数据库连接数打满应用日志里全是超时。那一刻我脑子里蹦出来的词就是CC攻击。这不是我第一次被CC攻击搞得焦头烂额但每次复盘都会发现这类攻击最让人头疼的就在于看不见。它不像SYN Flood那样有汹涌的流量可供感知也不像暴力破解那样有清晰的失败日志可供追踪它就是让你服务变慢、变卡、最终不可用而你打开监控面板时甚至找不到明显的异常流量。这篇文章我想把这几年和CC攻击对抗的完整思路整理出来——从它到底看不见在哪到怎么把伪装撕开再到防御体系怎么从浅到深一层层搭起来。1. 表面看起来一切正常服务器却在垂死挣扎CC攻击到底看不见在哪1.1 一次告警背后的真实场景那天晚上我最终确认问题花了将近40分钟不是因为工具不够而是因为一开始的排查方向完全错了。带宽正常、入向流量平稳、机房层面没有丢包按照传统DDoS的排查经验我一度怀疑是代码BUG或者数据库慢查询。直到我把Nginx访问日志按秒拉出来才看到触目惊心的数字某一个商品查询接口每秒请求量从平时的20左右暴增到3000多。日志里的请求长得非常正常——User-Agent是常见的浏览器标识Referer五花八门请求参数也都是合法结构甚至每隔几秒还会换一个IP。这就是CC攻击最经典的伪装从单条请求看每一笔都是合法用户行为从整体看量级已经完全压垮了应用层计算资源。1.2 CC攻击与流量型DDoS的本质差异很多人把CC攻击和DDoS混为一谈实际上两者打的层次完全不同。流量型DDoS比如UDP Flood、ICMP Flood攻击的是网络层和传输层核心手段是灌满带宽或者耗尽防火墙连接表。这类攻击的特点是猛监控上很容易看到入向流量从几百兆跳到几十G防御思路也相对清晰——上高防IP、做流量清洗。CC攻击Challenge Collapsar的缩写历史原因沿用了这个名字打的是应用层。攻击者模拟真实用户的浏览行为不断向Web应用发起看起来完全合法的HTTP请求。它不追求大流量追求的是并发数和请求频率。单个请求可能只有几百字节但架不住量太大、太密集Web服务器光是接收请求、解析报文、调度进程、执行逻辑、读写数据库就已经被拖垮了。我用一张表来对比这两者对比维度流量型DDoSCC攻击攻击层次网络层/传输层应用层核心资源消耗带宽、防火墙会话CPU、内存、数据库连接、应用线程流量特征入向流量暴涨、包数量巨大总流量不大、QPS奇高、请求合法监控可见性带宽曲线一眼可见带宽正常、应用响应变慢主要危害链路拥塞、无法访问应用假死、业务不可用防御侧重点流量清洗、高防IP限流、验证码、WAF、缓存降级这个区别直接决定了为什么CC攻击看不见——我们习惯用带宽和连接数来判断是否被攻击但CC攻击恰恰绕开了这两个指标。如果监控体系只盯着网络层应用层被打死的时候你很可能还在排查数据库慢查询。1.3 为什么每个请求都是合法的才是最棘手的流量型DDoS的报文一眼就能识别出异常比如源IP伪造、协议栈不完整、包大小异常。而CC攻击的请求是从真实浏览器环境中模拟出来的完整的HTTP头、合理的Accept-Language、正常的Cookie行为、符合业务逻辑的请求参数。这就带来了一个巨大的难题你没法通过请求是否合法来决定是否拦截因为每一笔都合法。传统防火墙在这里几乎失效IPS的签名特征也很难覆盖——因为攻击脚本可以无限变化UA、Referer、URL参数你今天封掉的组合明天就换了新的。我后来在内部复盘时打过一个比方流量型DDoS像有人抡着大锤砸你家的门你一眼就能看到CC攻击像一群穿着和你家客人一模一样的衣服、拿着一样的请柬的人假扮成客人不停地进进出出把你家的沙发坐满、把茶水喝完、把通道堵死。你真要拦还得分清楚谁是客人谁是坏人。2. 攻击方的成本账用三分力气让你付出三十分代价2.1 资源不对称原理请求很廉价处理很昂贵CC攻击能够持续这么长时间、这么难防根本原因在于成本极不对称。攻击方发起一个HTTP请求的成本几乎可以忽略不计——一台普通的攻击机器单线程每秒能构造并发出几十个请求如果用自动化脚本配合多线程、协程单机每秒几百上千个请求毫无压力。而服务器处理一个请求的成本则高得多接收连接、TCP握手、TLS协商、解析HTTP头、路由匹配、鉴权、参数校验、查询数据库、渲染模板、返回响应这一整条链路的开销可能是攻击方的几十甚至上百倍。这就形成了典型的资源消耗战攻击方用很低的成本持续施压你的每台服务器却要用昂贵得多资源去应对。我见过一个真实案例攻击方用了不到两千台肉鸡组成的网络每台每秒只发5个请求加起来QPS才一万左右就把一套8核16G的应用集群打到CPU满载、接口大面积超时。单看每台的请求量甚至不够触发共用的限流阈值——这是分布式CC最狡猾的地方把总量打散到大量IP上每个IP都不够可疑。2.2 高频刷新、慢速连接、随机伪装三种常见形态从攻击行为特征上看CC攻击大致有三种形态防御时需要分别对待。第一种是高频请求型也是最常见的。攻击脚本盯着一个或几个消耗资源大的接口比如搜索、查询、报表导出以极高的频率反复请求。这类攻击的特征非常明显单一URL的请求量占比异常高QPS曲线呈尖峰状来源IP遍布各地。第二种是慢速连接型学术名叫Slowloris类攻击。攻击方建立HTTP连接后不发送完整请求而是每隔几十秒才发送一个字节的头部数据把服务器的连接池占满。服务器为了等待完整请求不得不一直保持这些连接最终导致正常的用户连接无法建立。这类攻击不需要高并发量几百个并发连接就能拖垮一台配置一般的服务器监控上表现为并发连接数奇高、但带宽和CPU却相对平稳。第三种是随机伪装型也是最难防御的。攻击脚本每一次请求都会随机生成UA、Referer、Cookie甚至随机切换URL参数让请求看起来分散在各种业务路径上。这类攻击的特征是整体QPS升高、但没有一个单一维度特别突出给基于频率统计的告警和拦截带来了很大麻烦。2.3 被误判为正常波动的典型特征我接触过不少被CC攻击打了几天才发现的案例复盘时发现共同点监控告警阈值设置得太粗。很多团队的告警看的是5分钟平均QPS这个粒度对CC攻击来说太钝了。攻击方只要把请求控制在每秒正常峰值的2-3倍以内5分钟平均下来就可能被平滑到阈值以下。真正有效的监控维度应该是秒级或者百毫秒级的P99响应时间和错误率这两个指标对资源争抢非常敏感。CPU从30%跳到90%可能要几分钟但P99响应时间从80ms跳到2秒只需要攻击方把并发提上来的一瞬间。还有一个典型的误判场景CC攻击的请求如果集中在某个动态接口数据库的慢查询日志会大量出现之前从未见过的慢SQL。这时候非常容易把方向带偏让人以为是代码上线引入了性能问题。我见过有团队花了半天去优化SQL、加索引结果该被攻击的接口还是被打死——因为问题根因根本不是SQL效率而是请求量超出了一个数量级。3. 接到告警后的完整排查链路从Nginx日志到数据库连接3.1 第一步不依赖监控面板先看access.log按分钟聚合监控面板在高负载下可能延迟甚至失准最可靠的排查起点是Web服务器访问日志本身。我习惯使用Nginx下面的思路也以Nginx为主但换成Apache访问日志也一样关键是命令思路。# 统计最近1分钟里每个URL的请求次数按降序排列 awk {print $7} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 # 按分钟切片统计整体请求总量看增长率 tail -50000 /var/log/nginx/access.log | awk {print $4} | cut -c1-16 | sort | uniq -c第一条命令的作用是立刻确认请求是否集中在某一个URL。如果某一个接口的请求量占到了总数的80%以上基本可以断定攻击目标就是它。第二条命令则用来确认增长的曲线形态——CC攻击的请求量增长通常是断崖式的一瞬间从正常水位跳到高位这和业务突发比如限时抢购的渐进式增长有明显区别。我还会顺手看一眼状态码的分布# 查看最近请求的状态码分布 tail -50000 /var/log/nginx/access.log | awk {print $9} | sort | uniq -c | sort -rn正常的业务流量状态码应该以200为主其次是304、404。如果出现大量499客户端主动断开通常是攻击方请求排队超时后主动放弃、502或者504说明后端应用已经过载请求处理不过来。这时候脑子里的第一个判断应该是应用层已经被拖垮了。3.2 第二步看来源IP和连接状态识别分布式攻击确认URL集中之后下一步是判断攻击是不是分布式的。这一步决定了很多防御手段怎么选。# 统计请求量最高的前20个来源IP awk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20如果Top IP的请求量就占了总请求量的绝大部分说明攻击源比较集中可以用IP维度的限流快速止血。但更多时候你会发现Top IP的请求量很平均——每个IP每秒可能只有十几二十个请求没有哪个IP特别突出。这就是我说的分布式CCIP维度限流基本失效必须靠行为维度的策略。紧接着要确认系统的连接状态# 查看服务器的TCP连接状态分布 ss -ant | awk {print $1} | sort | uniq -c重点关注两个状态TIME_WAIT和ESTABLISHED。如果TIME_WAIT数量暴增说明有大量短连接在快速建立和断开——这是高频请求的典型特征如果ESTABLISHED大量积压且持续不降则要怀疑是慢速连接型攻击连接建立后一直不发完整请求把并发连接池占满了。3.3 第三步顺着链路往应用和数据库走网络层和Web层看完之后如果还没法完全定位就需要往应用层深处走。这一阶段我通常按应用慢还是数据库慢来分叉。先看应用服务器指标# 监控CPU和负载 top -n 1 # 查看Java应用线程状态以Java为例其他语言看对应工具 jstack pid | grep -A 5 java.lang.Thread.State | grep RUNNABLE | wc -l # 查看数据库当前连接数以MySQL为例 mysql -e SHOW STATUS LIKE Threads_connected;排查逻辑是这样的如果CPU烧满、数据库连接数打满说明入口层的请求量已经把应用进程打成了只接收不处理的状态问题在流量端如果CPU不高、但某个接口的时延明显上升那要重点看该接口内部是否有锁竞争、慢查询或者外部依赖阻塞这时候再用慢查询日志确认。我踩过的一个坑是在CPU已经飙到95%的时候去开top看哪个进程消耗CPU结果发现CPU全都消耗在操作系统层面的网络中断处理和进程调度上而不是应用代码本身。后来才明白高并发短连接场景下CPU有很大一部分被内核协议栈和线程上下文切换吃掉了。这也是CC攻击区别于普通性能问题的特征之一。3.4 排查过程中常见的错误方向不要只盯流量这里必须单独提醒一下CC攻击的排查一定要从应用层向上看而不是从网络层向下看。我见过太多人接到告警后第一反应是打开流量分析系统找大流量发现没流量之后就开始怀疑硬件故障、运营商链路问题白白浪费一两个小时。正确顺序应该是应用响应变慢 → 查访问日志请求量 → 确认URL和IP分布 → 查系统连接状态 → 查应用线程和数据库连接。整个链路走下来正常情况下10分钟之内就能确认是否CC攻击、攻击目标和大概的攻击规模。我也建议每支运维团队把这套排查命令整理成文档放在手边真到告警响起来的时候人都容易慌有一份checklist照着做会从容很多。4. 防御从浅到深频率限制、验证码、WAF规则与缓存降级第3节讲的是被打时怎么快速确认这一节说怎么防。我的观点是没有一套配置能永远生效防御必须做分层。每一层拦掉一部分攻击流量让真正到达应用层的恶意请求降到应用能承受的量级以下。4.1 第一层Nginx 频率限制和验证码对于普通规模的CC攻击Nginx自带的limit_req模块就能解决大部分问题。它的原理是令牌桶算法控制每个IP在单位时间内能通过的请求数。我常用的配置如下# 定义限流区域每个IP每秒平均放行5个请求允许瞬时突发10个 limit_req_zone $binary_remote_addr zoneperip:10m rate5r/s; server { location /api/ { limit_req zoneperip burst10 nodelay; proxy_pass http://backend; } }注意几个容易出错的地方。第一$binary_remote_addr比$remote_addr更省内存10M共享内存大约能存16万个IP状态业务量不大的场景足够用。第二burst要结合实际业务压测来定太小会把正常用户也拦了太大又起不到限流效果。第三限流返回给用户的是503最好在配置里加上自定义的错误页面逻辑否则用户看到白屏反而会加剧恐慌。不过频率限制有个天然软肋分布式CC攻击时每个IP的请求量可能根本不到阈值。所以频率限制只能作为第一层。真正的强验证要看验证码和JS挑战。当检测到可疑的高风险行为时比如某个IP在短时间内访问了超过正常用户次数的页面、或者触发了Web应用防火墙的高风险规则强制要求完成一次滑块验证或点击验证能非常有效地把自动化脚本挡在外面。自动化的成本被拉高了攻击方要么放弃要么得花钱接入打码平台这会让攻击的ROI变得很低。需要注意的是验证码策略要尽量放在最前置的网关层不要打进业务代码里。让业务代码去判断验证码会引入耦合而且如果攻击打的就是业务服务器本身验证码判断逻辑也跟着一起被打挂了。整理一下当前常见的验证方式选择验证方式防自动化效果用户体验影响适用场景频率限制中极低通用第一层防线滑块/点击验证高中风险触发时使用JS挑战/空闲检测中高低反脚本、反非浏览器环境设备指纹行为分析高低对特定接口做精准风控4.2 第二层缓存优先、动态接口降级很多被CC攻击打得特别惨的团队都有一个共同问题所有请求都穿透到动态层。哪怕是商品详情这种几乎不变化的页面每次请求都要走一遍数据库查询、模板渲染成本和风险都被放大了。正确的思路是能缓存的全缓存。静态资源图片、CSS、JS交给CDN承担这不用说动态接口也要尽可能地做结果缓存。比如商品信息、公告配置、分类列表这类读多写少的接口完全可以在Redis里缓存结果设置短TTL比如30秒到60秒数据库压力瞬间就能降下来。# 以Python Flask Redis为例伪代码示意 cache_key product:detail:{}.format(product_id) data redis.get(cache_key) if data is None: data query_mysql(product_id) redis.setex(cache_key, 30, data) # 30秒缓存 return data缓存之外还要做服务降级预案。我参与过的一个项目核心交易链路里混着一个报表统计接口平时没人在意CC攻击一来攻击方死死盯住这个接口打结果整个应用的所有接口都跟着超时——因为报表查询占用了大量数据库连接和CPU时间片。后来我们把报表接口做了异步化改造和独立资源池攻击打它的时候其他核心接口基本不受影响。这个经验后来形成了一条铁律明确哪些是核心链路、哪些是边缘能力边缘能力在高负载下必须能快速熔断降级。4.3 慢速连接型攻击的专项防护慢速连接型CC攻击用前面的频率限制很难防住因为攻击方每秒建立的连接数可能并不多但它把每个连接都吊在服务器上不释放。针对这类攻击互联网服务器里有专门的参数可以调# 调整请求头和请求体的超时时间防止慢速连接长期占用 client_header_timeout 10s; client_body_timeout 10s; # 限制单个连接传输速率防止慢速持续占用 limit_rate 512k; # 同时限制连接数 limit_conn_zone $binary_remote_addr zoneperipconn:10m; server { limit_conn peripconn 20; }client_header_timeout设成10秒的意思是如果客户端10秒内没有发送完整的请求头直接断开连接。这基本能废掉Slowloris类攻击因为攻击方专门就靠慢来拖垮服务器你不给它慢的权力它的攻击就失去了根基。这里有一个容易被忽视的问题超时参数并不是越小越好。太小会有误伤移动网络下用户网络抖动可能导致正常的请求头还没传完就被断开。建议结合业务实际情况压测10秒是一个比较保守但稳妥的起点。4.4 在线WAF规则特征识别与自动更新频率限制、验证码、缓存属于半静态的防御手段真正要做到动态对抗还得靠Web应用防火墙WAF的规则引擎。这部分我重点讲一下规则设计的思路而不是具体产品的配置。一个标准的CC防护规则应该从四个维度提取特征IP维度、会话维度、行为维度、资源维度。IP维度包括IP的信誉分、历史请求频率、是否来自数据中心IP段会话维度包括Cookie是否存在、访问间隔是否符合人类行为比如间隔太规律就是脚本特征、是否频繁更换UA行为维度包括访问路径是否跳跃性太大、是否总是访问耗费资源的接口资源维度包括单个会话的服务端执行时间、数据库查询次数等。把这些维度组合起来就能形成高可信的拦截决策。举个例子某个IP在5分钟内访问了200次其中全部请求都集中在搜索接口UA还是同一个老旧的浏览器标识Cookie每次都是新建的——这四个特征叠加起来是正常用户的概率微乎其微可以直接拉黑。关于WAF我还要强调一点规则必须能够动态更新。CC攻击的自动化脚本迭代很快你今天封掉的特征组合明天攻击方可能就换了。如果规则完全靠人工维护根本跟不上。现在主流的做法是引入半自动化的异常流量分析模块定期从访问日志中学习正常用户的访问画像然后以此作为基线去比对实时流量偏差超过阈值就触发拦截或验证码。这也是CC防御从规则对抗走向行为对抗的分水岭。5. 我把系统调好了为什么又一次被打崩——防御实践中容易忽略的坑5.1 误杀正常用户限流阈值太高拦不住、太低一场事故我把最常见的一个坑放在最前面讲限流参数设置不当会直接制造一次业务事故。有一次我们在配置Nginx限流时把rate设成了2r/s测试环境完全正常但上线后白天高峰期出现大量用户访问报503。排查才发现同一栋办公楼出口的NAT IP只有一个整层的同事都走同一个公网IP访问限流一开整个办公室的人都在同一秒被限住了。从那以后我规定限流阈值设置前必须做业务压测和流量分析了解NAT出口IP的聚合情况。同时配置里要给搜索引擎爬虫、内部监控探针、合作伙伴回调等合法的高频来源加上白名单否则内置系统的健康检查也会被当成攻击拦掉引发误告警甚至自动故障转移。5.2 攻击者绕过CDN直打源站IP第二个坑更隐蔽很多团队给Web服务套了CDN和WAF以为防护已经到位了结果攻击一打就崩。后来查登录日志发现攻击方压根没有通过CDN域名进来而是直接用扫描工具找到了源站的真实IP绕过CDN把请求直接打到了源站服务器上。要堵住这个口子需要做几件事源站IP不要暴露在域名解析记录里源站服务器只允许CDN回源IP访问在安全组或防火墙层面对来源IP做白名单其他来源一律拒绝如果有必须对外开放的端口一定要在这些端口前面也部署一道轻量级的应用层防护。5.3 防御规则的滞后性特征变化比规则更新更快第三个坑我在前面提到过但值得单独展开静态规则注定跟不上一线攻击的变化。攻击脚本的作者也很勤奋他们会分析WAF特征、观察哪些请求被拦截然后动态调整UA、调整请求间隔、随机化参数。如果防御方只是坐等人写规则然后发版整个流程走完攻击特征早就换了三遍。我的应对经验是两条腿走路一是把日志分析做成持续运行的模块异常检测不能靠人肉看要让系统基于统计模型自动发现偏离基线的请求模式二是保持和同行的信息互通定期关注样本分析、特征库更新的公开信息主动把最新的攻击特征同步到自己的规则引擎里。5.4 给团队留下一份经过验证的防御预案最后一条经验来自那一次凌晨的告警之后。我们在复盘时发现虽然各个防御组件都有了但整套体系从没做过一次完整的攻击模拟演练。真到攻击发生时连告警应该通知谁、由谁来决策开启JS挑战、哪些接口可以先降级、谁负责跟云服务商沟通应急防护这些问题都没有答案现场一片混乱。于是我们后来按季度组织一次CC攻击模拟演练运维负责执行排查链路研发负责确认降级接口的切换流程值班组负责验证告警有效性和通知时效。经过两轮演练再遇到类似攻击整个团队的反应速度和规范性已经完全不同。防御预案这个东西平时看着是文档真出事了就是保命的工具。在我个人的防守经验里对待CC攻击最关键的心态是接受一个现实你不可能做到100%的物理拦截但是你可以做到把攻击的影响控制在可承受范围内。频率限制、验证码、缓存降级、慢速防护、动态规则每一层多挡住一点应用层扛住的压力就小一些。系统的设计目标不应该是一台服务器硬抗全世界而是让攻击方在试图打垮你的时候付出的成本远远高于你承受的成本——这场账算清楚了CC攻击就真的没有那么可怕。
阅读完成 · 觉得有帮助?