先说一个很实在的现象:很多开发者和运维朋友第一次接触“Dos攻击”这个词,往往不是因为看了安全报告,而是某天早上到公司发现网站打不开了,CPU跑满、带宽跑满、Nginx日志里塞满了奇奇怪怪的请求,然后才一脸懵地开始查。你问了一圈,有人说是机房被攻击了,有人说是同行恶意打压,也有人甩给你一句“被人打了”,然后留你一个人面对满屏报警。
我第一次经历这种事的时候,也是这个状态。当时的系统还在单机部署阶段,一台服务器扛所有业务,结果某个下午业务方反馈页面刷不出来,我登上去一看:ss -s显示几万个SYN_RECV状态的连接,负载高到根本敲不动命令。后来才慢慢搞清楚,这就是典型的Dos攻击场景——只不过那次攻击不算大,很多新手团队根本意识不到问题出在哪。
这篇内容会从根本原理讲起,把Dos攻击是什么、为什么能打垮系统、平时怎么提前做好防护、真被打了怎么快速处置,完整拆一遍。适合后端开发、运维工程师、独立开发者,以及所有维护过线上服务的人参考。
1. 一种“不需要漏洞”的攻击方式
很多人有个误区,觉得攻击者必须找到系统漏洞才能搞破坏。但Dos攻击恰恰不是这样,它甚至不需要你系统里存在任何Bug。它打的不是代码逻辑,而是资源——带宽、CPU、内存、连接数、队列长度,这些东西一旦被耗尽,服务自然就不可用了。
1.1 单点Dos和分布式DDoS的区别
先说概念。Dos是单个来源发起的拒绝服务攻击,DDoS是分布式拒绝服务攻击,也就是大量来源同时发起。日常聊的时候大家经常混着叫,反正都叫“被打了”,但处理和追踪难度完全不同。
单点Dos比较好办,找到那个IP、封掉,基本就完事了。但现实里真正让人头疼的几乎都是DDoS,攻击源不是一个IP而是几千几万个,封不过来。攻击者手里控制着一大堆“肉鸡”或者直接调用付费的“攻击平台”,一个指令下去,几万台机器同时向你的服务器发包。这就是你发现自己服务器扛不住、但机房反馈“流量正常”的原因——因为攻击流量是真实存在的,只是它不是正常业务流量。
1.2 攻击的本质:占住你所有能干活的东西
我习惯把Dos攻击理解成“占着茅坑不拉屎”。正常用户来访问,需要经过:网络入口带宽 -> 负载均衡/防火墙 -> 服务器网卡 -> 内核协议栈 -> 应用进程。这一整条链路,每一个环节的容量都是有上限的,攻击者只需要把这链条上的任意一环填满,后面的环节就算再强,也没有数据能送进来。
所以你会看到不同表现形式的攻击:
- 带宽耗尽型:UDP Flood、ICMP Flood、放大攻击,这类攻击把网络出口堵死,你的服务器本身可能还没什么压力,但用户连不进来。
- 连接耗尽型:SYN Flood、TCP全连接攻击,这类攻击把服务器的连接表、并发数占满,导致正常用户无法建立连接。
- 应用层耗尽型:HTTP Flood(也叫CC攻击)、慢速攻击,这类攻击伪装成正常业务请求,反复消耗应用进程、数据库连接、CPU运算资源。
不同的攻击目标,对应完全不同的防御思路。不了解这一点,拿到防护方案也不知道该怎么配置。
1.3 为什么“打不死”的业务也存在
还有一种情况也得说清楚。并不是所有业务都会被Dos轻易打垮。如果业务本身就跑在海量集群后面、入口有多级负载均衡和流量清洗、每个节点都能水平扩展,那么小规模攻击打上来就像拿小石子砸瀑布,根本翻不起浪花。只有接入层薄弱、架构单点集中的系统,才会被一次中等规模的攻击直接送走。
所以,对Dos攻击的恐惧,本质上是对自身架构脆弱性的恐惧。这也是为什么同一场攻击,有的公司直接瘫痪,有的公司只是监控弹了个告警。
2. 核心攻击原理拆解:知道它为什么疼,才知道怎么治
要防御Dos攻击,你得先理解它为什么有效。很多时候不是防御手段不够多,而是你根本没搞懂攻击的机制,导致配置了一堆规则,却对真正的攻击流量毫无作用。
2.1 三次握手的“半连接”漏洞
TCP建立连接要经过三次握手:客户端发SYN,服务器回SYN+ACK,客户端再发ACK。这中间的第二步完成后,服务器会为这个连接分配一个控制块,放入半连接队列(SYN Queue),等待最后一步的ACK。
问题就出在这里。SYN Flood攻击会伪造大量源IP发送SYN包,服务器每收到一个就回复SYN+ACK,然后傻等ACK,但真实的ACK永远不会来。半连接队列是有上限的,一旦堆满,内核就会丢弃新的SYN包——正常用户连握手的第一步都完不成。
生活一点理解就是:餐厅门口有个等位区,恶意的人不断取号但不进店吃饭,等位区满之后,真正来吃饭的客人连号都取不到。
这个攻击最早上世纪就被发现了,内核后来引入SYN Cookie机制来应对,但SYN Cookie也不是万能的。它把半连接信息加密在SYN+ACK的序列号里,服务器不保存状态,等客户端真的回ACK后再验证。这种方式有效缓解了小规模SYN Flood,但对某些绕过手段还是会有压力,因为协议栈本身还要处理海量包。
2.2 UDP无连接特性与放大攻击
UDP协议本身没有握手过程,连接状态少,处理逻辑简单,因此攻击者可以伪造源IP,向你的服务器发送大量UDP包。服务器收到后,会发现根本没有对应的UDP socket在监听,于是回一个ICMP端口不可达——这些回包同样消耗带宽和CPU。
比直接UDP Flood更狠的是反射放大攻击。攻击者把目标IP写成受害者的IP,然后向互联网上大量的公共DNS服务器、NTP服务器、memcached服务器等发送查询请求。这些服务器不会检验源IP真实性,会真的把响应发到受害者IP。攻击者用很小的流量,就能诱导出几十倍甚至上百倍的响应流量。
这也是为什么很多企业明明自己机房带宽充足,还是会被几百Gbps的攻击打垮——出口带宽是真实的物理瓶颈,再好的防火墙在带宽被塞满时也什么都做不了。
2.3 HTTP层的慢速攻击与CC攻击
到了应用层,攻击手段更“阴”,因为流量看起来和正常用户完全一样。CC攻击的思路是:找到那些会触发数据库查询、计算密集、响应慢的接口,比如搜索接口、报表接口、导出接口,用大量并发请求反复调用,把一个接口的响应时间拖慢,最终导致后端线程池全部被占满,其他正常接口也跟着遭殃。
慢速攻击则更隐蔽。比如Slowloris,攻击者建立一个HTTP连接后,不断发送不完整的请求头,每次只发一两个字节,并且控制发送频率,让服务器认为“这个客户端还活着,只是网络慢”,于是连接一直占着不放。Web服务器默认都配有超时时间,但如果每个攻击者都开几百条这种“半死不活”的连接,连接数很快就被吃光,正常用户连握手都进不了。
这类攻击最讨厌的地方在于:传统防火墙根本识别不出来,因为每一条单一连接看起来都像真人。你封IP吧,攻击者IP池大得很;你不封吧,资源慢慢被耗死。
2.4 为什么攻击者总盯着协议“规则”打
如果把这些攻击手段归类,你会发现一个共同点:攻击者不是破坏协议,而是利用协议的正常机制。握手机制是好的,他就利用握手的等待机制;UDP允许无状态发送,他就利用无状态;HTTP允许长时间连接,他就利用长时间连接。这就像有人按着小区门禁的开门键不撒手,进出的人看着门一直开着却进不去——你说门禁坏了吗?没有,功能是正常的,只是被占用了。
理解这一点对防御很重要。如果你的防御思路是“找出非法流量并拦截”,在Dos攻击面前基本无效,因为攻击流量很可能每一项技术指标都合法。正确的思路应该是“保证正常用户有资源可用”,也就是做容量规划、动态调度、快速隔离,而不是试图把所有攻击流量一个不落地识别出来。
3. 检测与防御实操:从应急到长效保护
防御Dos攻击没有一锤子买卖,它是一套组合拳。我见过很多团队,被打了就联系云厂商开启流量清洗,扛过去了就关掉,过两个月又被打了再打开,往复循环。结果就是每次被打都是一场混乱,没有任何沉淀。真正靠谱的做法,是提前把检测手段、防护配置、应急流程都搭建好。
3.1 快速识别:你的服务到底是不是被Dos打了
很多服务异常现象跟Dos攻击会混在一起。突然的一大波真实用户流量、数据库慢查询拖垮接口、代码Bug导致死循环占满CPU,都会表现出“用户访问不了”。如果误判方向,排查一整天都找不到原因。
我判断的标准就这么几条:
- 看流量曲线:如果带宽/请求量在极短时间内拉起,形成接近90度的上涨曲线,那基本是攻击流量,真实用户访问增长通常平滑。
- 看连接状态:
netstat -ant或ss -s如果大量SYN_RECV、TIME_WAIT、ESTABLISHED挤占连接表,极大概率是SYN Flood或全连接攻击。 - 看请求特征:应用日志里出现大量相同请求路径、相同User-Agent、相同Referer,或者来源IP分布极度离散且无业务规律,大概率是HTTP层攻击。
- 看资源占用:带宽打满但CPU很低,可能是带宽型攻击;CPU打满但并发连接不算高,可能是应用层攻击;连接数暴涨但CPU和带宽都正常,可能是连接耗尽型。
这几类判断可以同时查看。快速定位攻击类型,比乱抓药重要得多。
3.2 基础防护的几道门槛
我自己给内部系统做防护,按从低成本到高成本排列,大概有这么几层。
第一层:网络层基础设置。内核参数里调整tcp_max_syn_backlog、net.ipv4.tcp_syncookies、net.ipv4.tcp_abort_on_overflow,这些参数能提升小规模SYN Flood下的存活率。需要说明:调整参数只能提升容错空间,无法抵御大规模攻击,但作为基础水位是必要的。
第二层:带宽监控和告警。没有监控,就问不出“什么时候开始的”。带宽、CPU、内存、连接数、请求数,全部按分钟粒度监控,配置阈值告警。真被打了第一时间知道,才有可能在可挽回的窗口期内处理。
第三层:接入层限流和黑名单。Nginx支持limit_req和limit_conn做频率限制;云厂商的安全组、防火墙可以按IP做黑白名单。这套东西对小规模攻击有效,但注意频率限制的标准别设置得太窄,否则正常用户的大促、爬虫、合作方回源流量也会被误伤。
第四层:CDN和云高防。如果你的业务已经推到公网,又没有专门的安全团队,我强烈建议入口流量走CDN,核心业务再叠一层云厂商的高防IP或流量清洗服务。CDN能把静态资源分走,高防能把大流量攻击在云端清洗掉,源站看到的只是“相对干净”的流量。这东西不是花冤枉钱,是给团队买时间——被攻击时不用一个人熬夜改防火墙。
3.3 应急响应:被打了之后的一小时
假设现在你收到了告警,服务确实被攻击了,接下来怎么办。结合几次实战经验,我建议按以下顺序处理,别乱、别慌:
- 确认影响范围:先看是不是整个机房都挂了,还是只有某个域名、某台服务器遭殃。登录云控制台查看入方向带宽,确认攻击入口在哪。
- 止损优先:如果是小规模攻击,直接封可疑IP;如果封IP解决不了,先把服务切到备用IP或启用高防服务,让流量先洗一下再回源。
- 保留证据:攻击时间、流量峰值、来源IP、请求特征,全部截图记录。有些恶意竞争事件后续可能要提供证据,留存材料也能让供应商快速给出方案。
- 补防护:如果是黑洞封禁(被运营商暂时断网),立即联系服务商解封,同时确认防护套餐是否足够,不够就临时提升,别心疼钱,宕机损失更大。
- 复盘补强:攻击结束后把监控数据导出来,分析攻击方式、入口、持续时间,再决定是否需要调整架构。至少要做到:单点入口去掉,关键业务前置CDN,登录、接口等重要路径加验证码。
这里要特别说一个很多人忽略的点:攻击结束后不要马上把所有防护配置撤掉。Dos攻击经常打一阵子停一阵子,如果恢复正常后立刻拆掉防御,第二天可能又被打一波。至少保留高防和限流三个月以上,确认攻击源彻底放弃后再逐步调低防线。
3.4 如何用合适的方式应对应用层攻击
应用层攻击由于流量特征接近真实用户,建议配置方式要更精细:
- 对核心业务API按用户维度限流:比如单个用户每秒最多20次请求,超出后立即返回错误码或者触发验证码,这个不误伤正常用户,又能把恶意高频请求挡住。
- 对慢速攻击,调低请求头超时时间:Nginx的
client_header_timeout、client_body_timeout,从默认60秒降到10~15秒。攻击者发不完请求头就被服务器断掉,资源占用率立刻下降。 - 对需要登录的接口,增加设备指纹和验证码策略。攻击者要绕过这些验证,成本会大幅提高。
- 数据分布上,大项目可以考虑将登录、下单等关键服务做物理隔离,避免某个接口被“打爆”后整站不可用。
实操中我试过,单靠代码层限流拦不住分布式攻击,但代码层限流配合高防清洗,用户体验的损失会小很多。换句话说,你在每一层都挡掉一点,最终到达应用层的恶意流量就在可控范围内了。
4. 工具选型的思考与配置示例
防御Dos攻击不是堆硬件,也不是买越多越安全。工具选型要配合你的业务体量、团队运维能力和预算。这里用对比方式聊一下不同层面的方案选型。
4.1 各类防护方案的综合对比
| 方案 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| 内核参数调优 | 小规模SYN Flood、基础加固 | 零成本、生效快 | 无法抵御大流量攻击 |
| 云厂商安全组/防火墙 | 中小攻击、瞬时拦截 | 配置简单、即开即用 | IP封禁粒度有限,可能误伤 |
| CDN | 静态资源为主、分布式入口 | 分担带宽、隐藏源站IP | 动态API接入较复杂 |
| 高防IP/流量清洗中心 | 大流量DDoS攻击 | 容量大、清洗能力强 | 费用较高,切流有延迟 |
| 自建流量分析系统 | 长期监控、主动检测 | 数据沉淀好、策略可控 | 开发维护成本高 |
我的建议是:初创项目自建成本有限,优先用云厂商的高防;有一定运维能力的团队,加一层自建流量分析用于预警和复盘;只有大公司或核心业务,才值得自建一整套清洗系统。
4.2 一套服务器端的基础防御配置参考
这里给一套我在Linux服务器上实测过的配置组合,适合Nginx服务的常规加固。
# /etc/sysctl.conf 关键参数 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_max_syn_backlog = 4096 net.ipv4.tcp_abort_on_overflow = 0 net.ipv4.ip_local_port_range = 1024 65535 net.core.rmem_default = 1048576 net.core.rmem_max = 4194304生效方式:sysctl -p。解释一下几个参数:tcp_syncookies开启半连接防御;降低tcp_syn_retries避免服务器长时间等待不存在的客户端;适当调大tcp_max_syn_backlog给正常连接更多排队空间。
Nginx 层面,可以在http块内做基础限流。
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; limit_conn_zone $binary_remote_addr zone=perip:10m; server { listen 80; server_name example.com; location /api/ { limit_req zone=api_limit burst=40 nodelay; limit_conn perip 50; client_header_timeout 15s; client_body_timeout 15s; proxy_pass http://backend; } }注意:这里的20r/s和burst=40是我按一个中型内网接口场景配置的,生产环境一定按自己的QPS压测结果调整。配得太低,高峰时期正常用户会被挡在门外;配得太高,攻击流量也能轻易穿过来。没有捷径,只能压测。
4.3 长期水位监测的两种思路
如果团队有能力做主动检测,我推荐两种方式。一种是在接入层旁路部署流量分析系统,把NetFlow数据导出并做聚合分析,攻击流量在源头出现时就能通过“包速率异常、新建连接数暴涨、目的端口集中”等特征提前发现问题。另一种是给业务埋性能指标,定期统计核心接口的响应时间、成功率、调用来源分布,建立基线模型,当指标偏离基线两个标准差以上就触发告警。
这两种方案我都试过。流量分析系统偏重基础设施,适合有网络背景的团队;业务埋点偏重应用视角,适合开发主导运维的团队。不管用哪种,关键是提前做,而不是等到被打了才想“要不要加个监控”。
5. 常见问题排查与避坑指南
做防御这么久,踩过的坑比看过的文档多得多。下面这些问题几乎每个团队都会遇到至少一次。
5.1 常见问题速查表
| 现象 | 可能原因 | 处理手段 |
|---|---|---|
| 带宽打满但服务器CPU正常 | 带宽型UDP Flood或放大攻击 | 启用高防IP,封禁UDP端口或限速 |
| 连接数暴涨,带宽CPU都正常 | 全连接攻击,端口占满 | 调低空闲超时,按IP限连接数 |
| 大量SYN_RECV等待状态 | SYN Flood半连接攻击 | 开启SYN Cookie,调整半连接队列 |
| Nginx大量499状态 | 客户端等待太久主动断开 | 优化后端响应,排查应用层瓶颈 |
| 相同路径请求集中爆发 | HTTP Flood(CC攻击) | 接口限流,加验证码,动态封IP |
| 攻击恢复后几分钟又复发 | 防御配置太早移除 | 至少保留防护数周再逐步撤回 |
5.2 容易误判和误操作的地方
要说最坑的,还是自己人误伤自己人。有一次团队配置了IP黑名单,直接把某合作方出口IP误封了,导致对方回源请求全部404。排查了几个小时才发现,对方出口IP段恰好和攻击流量源段重叠。后来所有黑白名单都加了审批和超时自动解封机制,避免人工操作失误影响业务。
还有一次,我们在应急时为了快速止损,把公网入口的安全组规则改成了“拒绝所有”,结果把远程登录也一起断掉了。服务器失联,最后只能找机房管理员物理接显示器处理。这个教训让我养成了一个习惯:任何紧急配置变更前,先准备好带外管理通道和登录备用IP。
另一个容易被忽略的点是会话超时时间。之前配置过一段时间把keepalive_timeout调得很低,本意是减少连接占用,结果部分弱网用户每次请求都要重新建连,页面体验变得很差。应用层的参数调优,必须考虑真实用户场景,不能只从攻击视角出发。
5.3 怎样的防护算“及格”
我问过不少同行,大家的共识是:没有百分之百防住的攻击,只有提前准备好后的快速恢复。在小规模攻击下自动防御、不惊动人;在中型攻击下能扛住核心业务、把损失压到最小;在超大攻击下有降级预案,比如展示静态页面、下线非核心功能。能做到这三条,防护就算合格了。
真正的防御能力是在一次次攻击中积累出来的。我见过一个团队,每次攻击结束后都会做一次攻防复盘,把攻击类型、持续时间、拦截策略、误伤情况全部记录归档,下一轮迭代持续优化。半年之后,普通攻击已经不需要人工干预了。这种沉淀,比任何昂贵的防护设备都值钱。
我个人在实际运维中最认可的体会,还是“分层防御、及时止损”这八个字。不要指望某一层防护能解决所有问题,但也不要因为单点扛不住就放弃其他层的建设。把每一层该做的事做好,Dos攻击就只是一次流量波动,而不是一次业务事故。