四个连上就不说话的连接,把单线程服务钉住:正常请求从 15 毫秒变成 4027 毫秒, 多等 4.0 秒。压短读超时降到 816 毫秒,改成多线程只剩 2 毫秒。 这一招叫 Slowloris,几乎不花带宽。
前面十几节都是「拿到点什么」。DoS 是唯一一种「什么都不要,只要你别干活」。
一、它要的不是数据,是可用性
DoS 是一个来源把服务打瘫。手法有几种:占满带宽、占满连接、占满 CPU、让请求队列排到永远处理不完。 DDoS 是很多来源一起打(肉鸡、被入侵的摄像头和路由器)。 它解决的是「单个 IP 被封掉」这个问题,所以规模比手法重要。 这一类的目标写在名字里:让服务不可用。所以「数据没丢」不代表没被攻击——机密性和可用性是两件事。 最省资源的一种就是 Demo 测的 Slowloris:不占带宽、不占 CPU,只占连接。请求头发一半,然后不动了。
二、四个连接为什么能拖死一个服务
单线程服务端的模型很朴素:接一个连接、读一个请求、回一个响应,处理完才轮到下一个。 一个连接只发了半行请求头就不发了,服务端就在读上等着——不是在等数据,是在等超时。 这一等,后面所有连接全排队。 Demo 里超时设 1 秒、挂四个慢连接,正常请求要等 4027 毫秒,差不多就是四个超时叠起来。 所以这不是代码有 bug,是「一次只能处理一个」这个设计选择。并发模型直接决定抗打击能力。
三、两种缓解,通常一起上
压短读超时:Demo 里超时从 1 秒降到 0.2 秒,正常请求从 4027 毫秒降到 816 毫秒。 它没有消灭问题,只是把代价按比例压小——超时乘连接数,就是这个量级。 每个连接一个线程(或事件循环):Demo 里四个慢连接同时挂着,正常请求只花 2 毫秒。 慢连接再多,也只占内存,不占处理能力。 真实系统一般两个都上:超时防「一个连接无限挂着」,并发防「一个挂住连累所有人」。 外面再套一层反向代理或流量清洗,把明显异常的连接挡在门外。 还有一条容易漏的:限制单个 IP 的并发连接数。 Slowloris 靠的就是「一个 IP 开很多半开连接」,把这条限住,成本立刻上去。
四、动手 Demo:四个不说话的连接,多等 4 秒
本机起一个单线程服务端,先量一次正常请求要多久。 再开四个连接、每个只发半行请求头就不发了,重新量一次正常请求。 然后把读超时压短、再改成多线程,各量一次。
def hang(n):
for _ in range(n):
s = socket.create_connection(ADDR)
s.sendall(b'GET / HTTP/1.1\r\nHost: x\r\n') # 发一半就停
SLOW.append(s)
t0 = time.perf_counter()
st, hd, bd = H.recv_response(socket.create_connection(ADDR), REQ)
print(u'%d 毫秒' % ((time.perf_counter() - t0) * 1000))
实跑输出:
--------------------------------------------------------------------
DoS:四个不说话的连接能拖垮一个服务
--------------------------------------------------------------------
场景 正常请求 说明
单线程,没有慢连接 15 毫秒 基准
单线程,4 个慢连接(超时 1 秒) 4027 毫秒 被拖住
单线程,4 个慢连接(超时 0.2 秒) 816 毫秒 拖得少些
多线程,4 个慢连接 2 毫秒 几乎没影响
正常请求被拖住 多等了 4.0 秒(4027 毫秒 对 15 毫秒)
这四个慢连接什么坏事都没干:它们只是**连上了却不把请求发完**。
单线程服务端一次只能处理一个连接,所以它被第一个慢连接钉住,
后面排队的全得等——包括那些正常的请求。
这一招叫 Slowloris。它的可怕之处在于**不需要带宽**:四个连接、
每秒几个字节,就能让一个单线程服务形同虚设。
两种缓解都在这张表里:把每连接的读超时压短(C),
或者干脆每个连接一个线程(D)。真实系统一般两个都上,
四行数字:15、4027、816、2 毫秒。基准 15,四个慢连接挂上之后 4027,多等 4.0 秒。 第三行说明超时确实有用:0.2 秒超时下只多等了 0.8 秒。 但注意它是按比例压小的——1 秒超时配 4 个连接,就是 4 秒。 想彻底解决得靠并发,不是靠把超时调到很小(那会误杀正常用户)。 第四行是多线程版:慢连接照样挂着,正常请求 2 毫秒。 差别不在「线程更快」,在「它不用等前面那一个」。 所以这一节该记的不是 4027 这个数,是 15 对 4027 这个比例:打垮服务的是排队,不是计算。
30 秒实验
- 本机起一个
python -m http.server,用几个 telnet 连着不发请求,看它卡不卡。 - 想一想:为什么「不限单 IP 并发」等于给这类攻击帮忙?
- 查一个你用的云服务的连接数上限,想想它为什么要有这个数。
常见误区
- 「带宽够大就不怕」——Slowloris 每秒只发几个字节。
- 「上了 HTTPS 更安全」——握手更贵,这一类攻击反而更省对手的力气。
- 「数据没丢就不算被攻击」——它打的是可用性,不是机密性。
自测三问(点开对答案)
1. Slowloris 占的是什么资源? —— 连接和读超时,几乎不占带宽,也不占 CPU。 2. 为什么单线程服务特别怕它? —— 一次只能处理一个连接,前面那个挂住,后面全排队。 3. 两种缓解各解决哪一半? —— 超时缩短「单个连接挂多久」,并发解决「一个挂住是否连累别人」。最后一种最不像攻击——因为它可能就藏在自家代码里。
- ☐ (本节完,下一节 11.5.4 后门程序)