【辟谣】hyper 存在拒绝服务漏洞 ??? Rust 项目易受 DoS 攻击???真相在这里
引子
近日,社区疯狂流传一篇被标题为“Hyper 存在漏洞,Rust项目易受拒绝服务攻击”的文章。
当然,这篇文章来自于国外,原文是 JFrog (JFrog是 Rust基金会白金成员)官方博客发布的名为“使用 Rust 流行的 Hyper 包时注意 DoS” 的文章。
其实,JFrog 的人在几个月之前就联系过 Hyper 作者 seanmonstar
seanmonstar 在 hyper 相关 issues 里说到:
“我知道这篇文章,他们在几个月前私下联系了我,我试图解释它与
Read::read_to_end
本质上是一样的,但他们觉得声称 hyper 易受攻击的大标题对他们有用。🤷
JFrog 作为 Rust 基金会白金成员之一,起这个标题,到目前为止我是可以理解的,至少这个标题可以呼吁 Rust 开发者在使用 Hyper 时要注意这个 Dos 风险。
但让人更无语的是,国内技术媒体翻译到国内以后,标题被改的已经失去了原文的初衷,扭曲了事实。
接下来 请大家耐心看完,就知道真相是什么了。
hyper Dos 漏洞是什么鬼?
首先必须得承认,原文中所说的 hyper 当前最新稳定版(v0.14)中 to_bytes
函数确实存在 Dos 风险。但其实,这并不是 hyper 的漏洞。
“JFrog 安全研究团队不断在流行的开源项目中寻找新的和以前未知的漏洞和安全问题,以帮助改善他们的安全状况并保护更广泛的软件供应链。作为这项工作的一部分,我们最近发现并披露了流行的 Rust 项目(例如Axum、Salvo和conduit-hyper )中的多个漏洞,这些漏洞源于相同的根本原因——在使用 Hyper 库时忘记对 HTTP 请求设置适当的限制。
这是原文中的开篇,清楚地写道:“漏洞是属于一些较为流行的 Rust 项目(例如Axum、Salvo和conduit-hyper ),这些漏洞源于相同的根本原因——在使用 Hyper 库时忘记对 HTTP 请求设置适当的限制。”
是这些 Rust 项目,使用 Hyper 库时,自己忘记了对 HTTP 请求设置做适当处理导致的。
而在 Hyper 的文档中,to_bytes
这个函数的文档,清清楚楚地写了注意事项:
“如果远程数据不受信任,则需要小心。该函数不执行任何长度检查,恶意方可能会消耗任意数量的内存。检查
Content-Length
是一种可能性,但并不严格要求必须存在。
文档里提醒开发者使用这个函数需要小心,并且也解释了为什么在这个函数内部没有对其做限制。那些 Rust 项目使用了该函数,引发了DoS 漏洞,完全是因为没有看该文档导致的。换句话说,是使用不当导致的。
所以,结论是,hyper 并不存在什么 DoS 漏洞。
to_bytes
函数是否应该标记为 unsafe ?
由此引发的另一个讨论是,to_bytes
函数既然使用不当会导致 DoS 拒绝服务漏洞,那么这个函数是不是应该被 unsafe
标记?
需要知道的是,unsafe fn
只代表可能引发 未定义行为(UB)有内存安全风险的函数。而 DoS 拒绝服务漏洞,和内存泄漏问题类似,都不属于内存安全范畴,所以在函数的定义上,不需要加 unsafe
标记,否则会让调用者误解它可能会引发UB。
再次重申,unsafe
用于标记 safety
(功能安全)风险的问题,而非 security
(信息安全)风险的问题。
另外,也有朋友说,开发者大部分人不喜欢看文档,像这种有 Security 风险的 API ,是否能在命名上提醒调用者注意呢?
unsafe
关键字是有编译器来管理的,但是对于 security
而约定的命名规范,编译器没有办法管理,这只能靠自觉了。
“如果非要命名的话,我建议增加
rtfd_
前缀(Read The Fucking Doc)。(狗头
如果非要命名的话,我建议增加 insecure_
前缀。
“P.S 针对这个问题,也有人提出另外的解决方案,就是增加一个
size_limit
参数。
标准库文档中 Read::read_to_end
的拒绝服务风险
hyper 作者也提到了,to_bytes
的本质和 Read::read_to_end
一样。read_to_end
函数会读取给定源中直到 EOF 的所有字节,将它们放入buf
。该函数也没有对给定源数据做任何大小限制。并且 Rust 中有很多函数都是这样的,开发者需要自己去阅读文档评估风险。
请记住, Rust 并不保证没有 Security 的安全风险。
但是对于 read_to_read
的拒绝服务攻击成本相比较 to_bytes
更高,前者需要攻击者真正提供MB 或 GB 级别的数据才能执行 Dos,但是后者的攻击成本很低,只需要设置 Content-Length 长度就可以让 to_bytes
去分配内存。
这里也许可以给我们一个安全启示:在设计 API 的时候,不要把内存分配的权限给暴露出去。
目前 hyper 作者给 0.14 稳定版发布了一个补丁:
// Don't pre-emptively reserve *too* much.
let rest = (body.size_hint().lower() as usize).min(1024 * 16);
let cap = first
.remaining()
.saturating_add(second.remaining())
.saturating_add(rest);
这样的效果是,让 to_bytes
分配的内存空间最多只保留~16kb。
但是,在调用该函数之前检查长度仍然是最好的方法。
目前该 PR 并未合并。
hyper 1.0 中已经删除了 to_bytes
在最新的 hyper 1.0 rc2 中,body::to_bytes
已经被删除。取而代之的是,http_body_util::BodyExt
中的两个新方法:limit(max)
和 collect()
,用法是 body.limit(SOME_MAX).collect()
。并且 hyper 作者也正在考虑 增加一个 to_bytes_with_max(body, max)
方法,并将标记 body::to_bytes
已经被弃用。
小结
综上所述,hyper 本身并不存在 DoS 漏洞。我们在使用 Rust 开发程序的时候,不要盲目地认为 Rust 是 百分百安全的。Rust 可以排除绝大部分内存安全风险,但并不能保证没有 Security 风险。
作为开发者,在调用第三方接口时,最好仔细阅读文档,才能让系统更加健壮。
感谢您的阅读。