在使用Rust流行的Hyper包时要注意DoS

JFrog安全研究团队不断在流行的开源项目中寻找新的和以前未知的漏洞和安全问题,以帮助改善他们的安全状况,并保护更广泛的软件供应链。作为这项工作的一部分,我们最近在流行的Rust项目中发现并披露了多个漏洞,比如阿克苏姆,齐射和conduit-hyper,这是由同一个根本原因引起的忘记对HTTP请求设置适当的限制使用Hyper库时。在这篇博文中,我们将详细阐述这个问题,并分享如何避免它的指导,这很重要,因为它可以被DoS轻易利用。
什么是Rust的Hyper Package?
超是一个非常流行的、用Rust编写的低级HTTP库。该库不是一个功能齐全的HTTP服务器或客户端,但它可以用作实现这些功能的“构建块”,因为它包含响应请求、解析请求体和生成适当HTTP响应的方法。目前,这是Rust中最受欢迎的HTTP库,下载量超过6700万次crates.io因为它对于构建功能更丰富的HTTP客户端和服务器非常有用。两个最流行的基于rust的HTTP客户端和服务器,即reqwest和经,建在Hyper之上,目前总共有2579箱子里的项目。io依赖于Hyper。
最基本的弱点是无限制地消耗资源
Hyper API中一个非常常见和有用的函数是身体:to_bytes,该函数用于将请求或响应体复制到单个对象字节缓冲区,例如下面的不安全用法-
pub async fn to_bytes(body: T) -> Result where T: HttpBody,{…//如果有1个以上的buf,我们首先要平化为Vec。让cap = first.remaining() + second.remaining() + body.size_hint().lower()作为usize;let mut vec = vec::with_capacity(上限);…
上述呼叫不安全的原因也在函数的文档-该函数不执行任何长度检查。因此,由于该函数将整个主体写入单个缓冲区,因此可以使该函数分配任意数量的内存,与恶意HTTP数据包的大小成正比。

更危险的问题是——即时拒绝
一些供应商可能忽略了上面的警告,或者简单地淡化了这个问题,因为发送一个体大小为64GB的HTTP请求(或任何会占用所有可用内存的大小)是非常不可行的——传输的数据量将是巨大的,在现实世界中,这个请求将被代理、cdn、waf等停止,仅仅是因为它的异常大小。
但是,如果没有任何长度检查,实际上有可能滥用此问题导致DoS即使是很小的一包。
如前所述,to_bytes函数读取数据块。如果只有一个数据块,它就返回它。在读取第一个块之后,代码检查是否有更多内容要阅读。如果在线上没有其他东西等待,代码返回读取的第一个数据块。如果在线上有更多的数据,则代码创建一个容量为物体预期长度的向量- - - - - -
pub async fn to_bytes(body: T) -> Result where T: HttpBody,{…//如果有1个以上的buf,我们首先要平化为Vec。让cap = first.remaining() + second.remaining() + body.size_hint().lower()作为usize;let mut vec = vec::with_capacity(上限);…
然后,代码用已经读取的数据填充vector,然后等待将其余数据读入vector。
这里的关键观察是向量的大小来自“Content-Length”头。预期的大小直接传递给Rust内存分配器,如果预期的大小对于分配器来说太大,它会恐慌并使进程崩溃。由于“Content-Length”报头的值没有限制,因此有可能发送一个带有非常大的“Content-Length”值的小请求,这将立即导致进程崩溃
日志含义分配11111111111111111111111111字节内存失败
在许多情况下,根据使用Hyper的项目,这可能是零点击DoS攻击,因为基于http的服务器从不受信任的来源接收数据的场景非常常见。一个这样的例子我们披露的在Axum web应用程序框架中,使用该框架构建的每个web应用程序在默认情况下都容易受到此问题的影响。
如何解决这个问题?
由于Hyper库默认情况下不限制HTTP正文大小,因此依赖Hyper的开发人员可以通过比较请求/响应来在自己的代码中实现大小检查size_hint到某个上限。例如来自官方文档- - - - - -
使用超::{身体::HttpBody};让response = client.request(request).await?;const max_allow_response_size: u64 = 1024;let response_content_length = match response.body().size_hint().upper() {Some(v) => v, None => MAX_ALLOWED_RESPONSE_SIZE + 1};如果response_content_length < MAX_ALLOWED_RESPONSE_SIZE {let body_bytes = hyper::body::to_bytes(response.into_body()).await?;println !(“身体:{:?}", body_bytes); }
总结
总而言之,使用Hyper时缺乏大小限制是一个非常严重的问题,攻击者很容易利用它来崩溃HTTP客户端和服务器。我们强烈建议如上所示对请求和响应实施大小限制。JFrog安全研究团队将继续提醒易受此问题影响的Rust维护者,以便修复此漏洞的所有实例。与JFrog安全研究保持同步