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

当使用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。Let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;let mut vec = vec::with_capacity(cap);...

上述调用不安全的原因也在函数的文档-该函数不进行任何长度检查。因此,由于函数将整个正文写入单个缓冲区,可以使该函数分配任意数量的内存量,与恶意HTTP数据包的大小成正比。

更危险的问题是即时DoS

一些供应商可能忽略了上面的警告,或者简单地淡化了这个问题,因为发送一个体大小为64GB(或任何将占用所有可用内存的大小)的HTTP请求是非常不可行的——传输的数据量将是巨大的,在现实世界中,这个请求将被代理、cdn、waf等停止,仅仅是因为它的异常大小。

然而,如果没有任何长度检查,实际上有可能滥用这个问题导致DoS即使是很小的一包

如前所述,to_bytes函数读取数据块。如果只有一个数据块,它只返回它。在阅读第一个块之后,代码检查是否有更多要读的内容.如果行上没有其他等待,则代码返回读取的第一个块.如果行上有更多的数据,那么代码创建一个具有body预期长度容量的Vector- - - - - -

pub async fn to_bytes(body: T) -> Result where T: HttpBody,{…//有超过1个buf,我们得先扁成Vec。Let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;let mut vec = vec::with_capacity(cap);...

然后,代码用已经读取的数据填充向量,然后等待其余数据被读入其中。

这里的关键观察是向量的大小来自于“Content-Length”头.预期的大小直接传递给Rust内存分配器,如果预期的大小对分配器来说太大了,它会恐慌并使进程崩溃。由于“Content-Length”报头的值没有限制,所以发送一个带有非常大的“Content-Length”值的小请求是可能的,这会立即使进程崩溃

分配1111111111111111111111字节内存失败

在许多情况下,这可能是零点击DoS攻击,这取决于使用Hyper的项目,因为基于http的服务器从不受信任的源接收数据的场景非常常见。一个这样的例子我们披露的在Axum web应用程序框架中,默认情况下,每个使用该框架构建的web应用程序都容易受到这个问题的影响。

如何解决这个问题?

由于Hyper库在默认情况下不限制HTTP正文大小,因此依赖Hyper的开发人员可以通过比较请求/响应来在自己的代码中实现大小检查size_hint到某个上限。例如,从官方文档- - - - - -

使用超::{身体::HttpBody};Let response = client.request(request).await?;const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024;let response_content_length = match response.body().size_hint().upper() {Some(v) => v, None => MAX_ALLOWED_RESPONSE_SIZE + 1};if 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安全研究保持最新