security

网站被 DDoS 打挂,先看哪一层撑不住

带宽被占满时 CPU 可以还是闲的,请求不多时应用也会被贵接口拖死。这篇按链路、连接和应用说明 DDoS 打的是哪一层,以及怎么把它和访问高峰、发布故障分开。

约五千一百字·读约十五分钟 · English

网站被 DDoS 打挂,先看哪一层撑不住

网站打不开的时候,群里常有人说“被 DDoS 了”。有时候确实是。但“很多人一起刷新”只能解释一部分情况。有的流量在请求到达网站之前就把链路堵住了;有的专门占用 TCP 连接;还有的请求数并不多,却让应用处理不过来。

DDoS 是 Distributed Denial of Service 的缩写,中文叫分布式拒绝服务攻击。很多来源同时向一个服务发流量,把带宽、连接或应用的处理能力用光,正常用户就进不来。

遇到这种故障,先弄清楚是哪一层被打满了,后面的判断才有依据。

CPU 不高,网站也可能打不开

打开一个网页,请求要经过好几个环节。下面是一条简化的链路。

用户、DNS、接入链路、网络设备、应用和数据库,每一段都有容量上限

图 1:瓶颈可能出现在带宽、连接处理或后端查询上,只看 CPU 不够。

接入链路每秒能传多少数据,是有上限的。路由器和防火墙要逐个处理数据包,有的还要为每条连接记状态。应用给每个请求分配线程和内存。数据库能同时跑的查询也有限。

哪个环节先到顶,用户就会觉得网站挂了。入口带宽被占满时,请求根本到不了服务器,CPU 可能还很闲。另一种情况是带宽够用、请求总量也不高,但每个请求都要校验权限、查数据库、拼页面,应用一样会卡死。

DDoS 攻击破坏的是可用性。攻击者不需要登录系统,也不需要数据库权限。网站打不开,不代表数据已经泄露。是否被入侵、数据有没有被偷,要另外查。

流量从哪里来

只有一个来源的攻击叫 DoS。多个来源一起发起,才叫 DDoS。

这些来源不一定在同一个网络里,也不一定是攻击者自己的电脑。

僵尸网络

最常见的做法是用僵尸网络(botnet)。攻击者用恶意软件控制一批联网设备,远程让它们发请求。这些设备可能是个人电脑,也可能是路由器、摄像头之类的物联网设备。

设备的主人通常不知道。目标看到的是许多不同 IP 发来的请求。一个一个封,很难跟上对方换 IP 的速度,还可能把和攻击设备共用出口的正常用户一起封掉。

来源多,流量不一定均匀。来源有多少、分布在哪里、请求长什么样,都要靠监控去看。光听到“分布式”这三个字,什么也判断不了。

反射与放大

另一种做法是借助公开的第三方服务。

攻击者把请求的源 IP 伪造成受害者的地址,第三方服务收到后,会把响应发给这个地址。这就是反射。响应落在受害者身上,发请求的人自己什么也收不到。

伪造源地址后,公开 UDP 服务的响应被送到受害者

图 2:反射决定包发往哪里,放大决定响应比请求大多少。被利用的第三方服务本身不一定被攻破。

放大指的是流量变大。一个小请求能换回大得多的响应,原始流量就被放大了。DNS、NTP、Memcached 等 UDP 服务都被用过。

反射和放大常常一起出现,但不是一回事。有的反射几乎不放大。响应比请求大,也不能说明包一定是冲着受害者去的。

被利用的服务通常只是对外开放、有请求就回应的服务。UDP 没有 TCP 那样的握手确认源地址,伪造起来比较容易。不过,并不是所有开着 UDP 端口的机器都能拿来反射。应用层做校验、网络层做过滤,都会让攻击难以成功。

三类攻击,分别耗尽什么

流量型、协议型和应用层,是按瓶颈位置给攻击分的类。一次攻击经常同时消耗几种资源,攻击者中途换手法也很常见。

流量型、协议型和应用层攻击各自对应的资源瓶颈

图 3:图里的例子不全,同一次攻击可能同时属于几个类别。

流量型:链路先被占满

流量型攻击的目标是带宽,让目标和互联网之间的链路堵住。UDP 洪泛和反射放大都能造成这种效果。

流量一旦进了接入链路,即使服务器马上丢掉,这段带宽也已经用掉了。服务器能认出垃圾包,用户还是打不开网站。所以大流量攻击一般要在上游处理,由运营商或容量足够的防护网络先过滤、分流或者直接吸收。

协议型:握手没完成,连接表先满

TCP 建连通常要三次握手。简化之后是下面这个过程。

TCP 正常三次握手,以及最终 ACK 未到达时的状态占用

图 4:正常连接依次完成 SYN、SYN-ACK、ACK。半开连接占多少资源,取决于具体实现和配置。

服务端发出 SYN-ACK 之后,如果客户端一直不发最后的 ACK,这条连接就停在半开状态。传统做法是保留这些状态,等客户端完成握手。

SYN flood 就利用了这一点。攻击者发出大量握手无法完成的 SYN,占满监听端口的半开连接队列(backlog),正常的新连接就排不进来。运维时可以统计 SYN_RECV 状态的连接数,看队列是不是被占满了。RFC 4987 讨论的就是这个半开队列被耗尽的问题,不是把整台机器的内存吃光。

队列多大、满了以后怎么丢弃,各家实现不一样。TCP 服务也不都是一碰就倒。

SYN cookies 是一种缓解办法。服务端把部分握手信息编码进 SYN-ACK 的初始序号里,不再为每个半开连接保存完整状态。收到能通过校验的最终 ACK,才真正建立连接。它能减轻“没握完手就先占资源”的压力,但不增加带宽,也替代不了应用层的检查。另外,各操作系统对 TCP 选项的支持并不一样。RFC 里关于早期方案的兼容性说明,不能直接拿来套用到所有现代系统。

应用层:请求很小,处理很贵

HTTP flood 打的是网站或接口的处理能力。请求格式完全正常,只是频率或分布不对劲,正常用户的请求就排不上队。常见手法有两种:反复请求搜索、登录、下单这类高成本接口;或者在 URL 后面加随机参数,让请求绕过 CDN 缓存,全部回到源站。

一个 HTTP 请求可能只有几百字节。服务器却要校验身份、查数据库,再生成页面或 JSON。

用一组简化数字看看差距。假设系统有 200 个固定并发槽位,每个请求处理期间占用一个槽位:

  • 平均占用 20 毫秒,理论上限约每秒 10,000 个请求(200 ÷ 0.02)。
  • 平均占用 200 毫秒,理论上限降到每秒 1,000 个请求(200 ÷ 0.2)。

这只是按比例算的理论值,不是任何网站的压测结果。异步处理、CPU 共享、数据库瓶颈和排队都会让实际数字偏离。

200 个并发槽下,20 毫秒与 200 毫秒请求的理想吞吐

图 5:两根柱子都从零画起,长度按 10,000 与 1,000 的比例。数字来自上面的假设,不是基准测试。

请求数一样,服务器的负担可能差十倍。只看总请求量,容易漏掉某一类高成本接口已经打满的情况。

低速攻击:慢也能拖垮服务

应用层攻击不一定要高频率。低速攻击让连接或请求一直不结束,慢慢占着有限的资源。Slowloris 就是典型例子:它只发送不完整的 HTTP 请求头,隔一段时间补一点数据,让服务器的连接一直挂着。

在按线程处理请求的服务器上,一个挂起的请求就占着一个线程。换成其他并发模型,受限的可能是连接数、缓冲区或别的请求状态,要看具体实现。

所以只盯“带宽突然暴涨”会漏掉这类攻击。连接持续了多久、活动连接有多少、超时请求是否堆积,也要看。

报告里的大数字

bps、pps、RPS 和连接数,衡量的是四件不同的事。

指标衡量什么先暴露问题的环节
bps每秒传输多少比特接入带宽
pps每秒处理多少个数据包网络设备的逐包处理能力
RPS每秒多少个 HTTP 请求请求频率;不反映单次成本,也不反映缓存命中
连接数同时占用多少连接长时间不结束的请求

大流量攻击一般先看 bps,因为链路先被带宽卡住。总字节数差不多时,大量小包会先把 pps 打满,设备先扛不住。RPS 只能说明 HTTP 请求有多密,不能说明每个请求花了多少资源,也不能说明有多少请求是从缓存返回的。

两个站点的 RPS 相同,一个请求很快结束,另一个请求一直挂着,压力可能完全不同。看到新闻里的大数字,要拿它和自己入口的容量对照,再看正常用户有没有变慢、报错或连不上。

变慢了,先别急着喊攻击

促销高峰和 DDoS 都会把服务压满。CPU 飙高、响应变慢、请求量猛增,这些都只是现象,本身不能证明是攻击。CISA 的建议是先建立正常流量的基线,再结合网络监控和日志判断。

先确定卡在哪一段

域名解析失败、TCP 连不上、应用返回错误,是三个不同的问题。首页能开、某个接口一直超时,又是另一种情况。

如果连接建立不起来,只看应用日志通常找不到原因。如果只是某类动态请求变慢,就该查应用和它依赖的后端服务。

和业务基线比,别和半夜比

工作日和周末的流量模式不同,发版和大促的情况又不一样。拿促销高峰去和平日凌晨比,很容易误判成攻击。

可以关注这几个信号:请求是否长时间集中在少数路径上,客户端行为是否机械地重复,带宽、连接数和错误率是否同时上涨。单看一个 IP 或一个浏览器标识,基本定不了性。

很多正常用户共用同一个公网出口。一个 IP 请求多,后面可能是整栋写字楼。反过来,攻击流量分散到大量 IP 上,看起来也可能像正常访问。

应用日志看不到被挡住的流量

被边缘节点拦下的请求,不会出现在应用日志里。链路已经堵死的请求,也到不了应用。所以应用日志要和边缘防护数据、网络监控、外部拨测一起看。

登录成功、正常的页面访问路径,能帮助判断用户在做什么。下结论之前,还要排除刚发的版本有问题,以及基础设施本身出了故障。

两起著名事件

2016 年 Dyn

2016 年 10 月 21 日,DNS 服务商 Dyn 遭到大规模 DDoS 攻击。攻击者用 Mirai 恶意软件感染的大量物联网设备组成僵尸网络。Twitter、Netflix、Reddit、GitHub 等网站在美国东部时间的部分时段无法正常访问。

这些网站本身并没有被直接攻击。DNS 负责把域名解析成 IP,解析链路断了,网站服务器即使还在运行,用户也找不到入口。所以网站可用性取决于整条依赖链,不只是自己的应用进程活没活着。

2018 年 GitHub

2018 年 3 月,GitHub 遭到一次利用 Memcached 的反射放大攻击,峰值流量约 1.35 Tbps。攻击者借助公网上没有做访问限制的 Memcached 服务器,把小请求放大成大量响应,指向 GitHub。这次攻击没有用僵尸网络。

防护方看到的源地址,可能是一批公开服务器,而不是攻击者的设备。两起事件暴露的问题也不同:Dyn 事件是公共依赖服务不可用,GitHub 事件是大规模反射流量的冲击。

清洗、CDN 与 WAF 各管一层

不同的瓶颈在不同位置,防护也要分层做。有些产品会把几种能力放进同一个边缘节点。图里按职责画出了各层,并不是要求按这个顺序在机房里摆设备。

上游清洗、CDN、WAF,以及限制来源的源站

图 6:命中缓存的内容在边缘直接返回;没命中或不能缓存的请求才回源。

上游清洗

清洗要尽量过滤掉恶意流量,把正常流量转给服务。清洗点要放在被打满的那段链路之前,清洗容量也要能扛住攻击规模。

分布式边缘网络还能把压力分散到多个节点。AWS 的防护指南把边缘容量和流量工程列为缓解大规模攻击的手段。

不过,服务商公布的网络总容量,不代表你的站点一定能扛住。接入路径怎么设计、防护覆盖哪些资源、攻击是哪一类,都会影响最终效果。

CDN:减少回源

CDN 在边缘节点缓存内容,一部分请求就不用回到源站。源站就是真正跑应用、存放内容的服务器。

图片、脚本、样式表最适合缓存。有些动态页面也能拆出可以共用的部分。不能缓存的请求,仍然要进后端。

AWS 文档介绍过请求合并(request collapsing):多个请求同时访问同一个还没缓存的对象时,条件满足的话,边缘节点只向源站发一次请求。这要看缓存配置,不能默认每次未命中都只回源一次。

判断源站受了多少保护,看缓存命中率和实际回源流量就够了。另外,个人账户页之类的内容不能为了省负载而缓存,否则不同用户会看到同一份数据。

WAF:检查 HTTP 请求

WAF(Web 应用防火墙)检查 HTTP 请求,按规则或速率决定放行、限制或拒绝,用来应对应用层的异常请求。

装在源站上的 WAF,救不回已经被占满的接入带宽。只有放在有网络防护能力的边缘平台上,WAF 才能和上游清洗配合,把恶意请求挡在源站之外。所以“装了 WAF”不等于网络层也安全了。

限速要按成本设计

限速给一段时间内可接受的请求量设上限,用来挡住持续的请求洪泛。如果所有接口都用同一条“每 IP 每秒 N 次”的规则,通常不合适。

来源分散的攻击可以绕开单 IP 阈值。正常用户共用一个出口时,又可能被一起误限。静态资源和高成本业务接口不该用同一条线。人机验证也不是每个接口都适合,浏览器访问和机器调 API 要分开处理。

评估规则时,两个数字都要看:攻击请求减少了多少,正常用户失败了多少。把正常用户限到打不开,本身就是一次故障。

源站要收口,扩容也会花钱

接了 CDN,源站入口还要收口

如果源站仍然允许任意 IP 直接访问,攻击流量就能绕过前面的边缘防护。只隐藏源站 IP 不够,还需要访问控制,必要时加上认证回源或专线。

IP 允许列表限制谁能连。认证用来确认请求确实来自预期的回源路径。健康检查这类合法访问也要留出通道。选哪种方案,要看维护成本和业务依赖。

配置出错可能把正常回源也挡住,上线前必须完整走一遍。DNS 里显示“已开启代理”,不能代替对整条链路的检查。

自动扩容加的是应用,不是带宽

加实例能提高应用的处理能力,但链路已经堵死的时候,加机器没有用。数据库或其他共享依赖先到顶,继续扩应用实例,吞吐也未必上去。

恶意请求还会触发自动扩容。服务可能没有完全宕机,账单先涨了。防护策略、容量上限和费用告警需要一起规划。

部分服务商在特定条件下会减免攻击相关的费用,但不覆盖所有请求、所有资源和全部扩容成本。能付钱扩容,并不代表这一层没有上限。

真遇到攻击时

CISA 的事件响应建议包括:确认攻击、启动应急预案、联系网络或托管服务商、保留证据。具体怎么缓解,要看现场表现,不能每次都套同一套规则。下面几件事可以同时进行:

  1. 先分清是整站不可用,还是某一部分变慢。记下受影响的入口和时间,定好负责人和沟通渠道。
  2. 把流量基线和网络指标、边缘告警、应用日志对照着看。同时排查是否刚发过版,或基础设施自己出了问题。
  3. 如果瓶颈在接入链路,尽快联系云厂商、托管商或运营商。这时再去调应用配置,通常来不及。
  4. 根据证据调整已有的清洗、访问控制或限速规则,看攻击是否减弱,正常用户有没有被误伤。保留关键日志、规则变更记录和时间线。
  5. 恢复后检查依赖服务有没有残留问题,再根据这次暴露出来的瓶颈补监控和预案。之前没覆盖到的入口,也要记下来。

保留证据不是无限制地记录每个请求。日志系统同样有吞吐、存储和费用上限,保留哪些字段、保留多久,要在出事之前定好。

事前准备

很多团队直到攻击来了才第一次看流量指标,往往已经晚了。下面这些建议提前做:

  • 建立正常流量基线,覆盖工作日、周末、发版和大促。
  • 摸清每一层的容量上限,包括带宽、连接数、源站并发和数据库连接池。
  • 找出高成本接口,给它们单独设计限速或缓存策略。
  • 确认源站无法被绕过访问,定期检查 CDN 到源站的回源链路。
  • 和云厂商或托管商确认 DDoS 防护的联系方式、升级流程和服务范围。
  • 写一份应急手册,说清楚谁来决策、怎么切换、哪些操作需要回滚。

收尾

评估防护效果,不能只看拦截了多少请求。更应该看正常用户能不能完成关键操作,错误率有没有降下来,延迟有没有恢复,源站和依赖服务是否回到了能持续承载的负载。

攻击流量很大,用户却还能正常用,说明防护起了作用。攻击流量已经降下来,用户还在大量失败,就要查过载后的依赖、误伤的限速规则,或者另有故障。应对 DDoS 的目的,是让服务恢复正常。

Mttao

Mttao GitHub ↗

探索技术与生活的智慧

相关文章

/ 评论