Windows平台演示
最早的内存炸弹是 zip 炸弹,也称为死亡 zip,它是一种恶意计算机文件,旨在使读取该文件的程序崩溃或瘫痪。zip 炸弹不会劫持程序的操作,而是利用解压缩压缩文件所需的时间、磁盘空间或内存。
zip 炸弹的一个示例是文件 42.zip,它是一个 42KB 的 zip 文件,包含 16 个 5 级深度的嵌套 zip 文件,每个底层存档包含一个 4.3 GB(4,294,967,295 字节;4 GiB - 1 B)的文件,用于总共 4.5 PB(4,503,599,626,321,920 字节;4 PiB - 1 MiB)的未压缩数据。
zip 炸弹的基本原理是,我们生成一个非常大的文件,其中充满 0(或其他值),然后将其压缩为 zip 文件。由于相同内容的文件的压缩比很高,因此生成的 zip 文件非常小。当受害者解压zip文件时,需要消耗大量内存来存储解压后的文件,从而迅速耗尽可用内存并导致目标因内存不足错误而崩溃。
在 Windows 上做一个简单的演示:
fsutil是windows上文件、卷管理命令。以管理员权限启动cmd并执行:C:\Windows\system32>fsutil file createnew test.txt 1073741824
生成1个G的TXT空文件。
接下来使用7z软件将txt压缩成为zip文件,发现压缩后只有1.2MB大小。
由此我们可以看出,使用zip对全0的文件的压缩比接近851:1。
事实上,任何压缩格式都可能成为内存炸弹,而不仅仅是 zip 文件。
我们可以继续实验,在Windows上使用7zip将填充0的1GB文件压缩成不同的格式。这给了我们以下压缩比:
文件格式及压缩后大小对比:
不同的文件格式支持不同的压缩算法。例如,zip文件可以使用Deflate、Deflate64、BZIP2、LZMA、PPMd等,每种都有不同的压缩比。上表显示了使用 7zip 默认算法的测试结果。
历史CVE漏洞
1.CVE-2023-3782
这是 OKHttp 库中的一个漏洞。OKHttp支持Brotli压缩算法。如果HTTP响应指定了Brotli压缩,由于OKHttp缺乏对“内存炸弹”攻击的防御,客户端可能会因内存耗尽而崩溃。
漏洞描述:
https://github.com/square/okhttp/issues/7738
漏洞修复:
https://github.com/envoyproxy/envoy/commit/d4c39e635603e2f23e1e08ddecf5a5fb5a706338#diff-88b327a1e72d55d1bb686b3b1f28f594b6b08139968304e6804a808fbb375ff0R26
2.CVE-2022-36114
这是 Rust 包管理器 Cargo 中的一个漏洞。从源代码存储库下载软件包时,Cargo 没有针对“内存炸弹”zip 文件的防御措施,导致恶意制作的 zip 在提取过程中填满磁盘空间。
漏洞描述:
https://github.com/rust-lang/cargo/security/advisories/GHSA-2hvr-h6gw-qrxp
漏洞修复:
https://github.com/rust-lang/cargo/commit/d1f9553c825f6d7481453be8d58d0e7f117988a7
3.CVE-2022-32206
这是流行的网络工具curl中的一个漏洞。版本curl<7.84.0支持“链式”HTTP压缩,这意味着服务器响应可以被压缩多次,可能使用不同的算法。这个“解压链”中接受的“链接”数量是无限的,允许恶意服务器插入几乎无限的压缩步骤。使用这样的解压链可能会导致“内存炸弹”,导致curl最终花费大量内存并因内存不足而出错。
漏洞详细信息:
https://lists.debian.org/debian-lts-announce/2022/08/msg00017.html
4.Sui区块链p2p协议中的拒绝服务漏洞
此漏洞可能会导致单个节点(验证器和全节点)崩溃。利用它非常简单 - 只需启动多个线程向节点发送有效负载即可触发崩溃,无需花费任何汽油费。Sui mainnet_v1.6.3(不含)之前的版本受影响。
漏洞详细信息:
https://beosin.com/resources/%22memory-bomb%22-vulnerability-causes-sui-node-to-crash?lang=zh-HK
补丁代码:
https://github.com/MystenLabs/sui/commit/42d4ad103a21d23fecd7c0271453da41604e71e9
建议
我们可以通过限制解压文件的大小来防御“内存炸弹”攻击。以下方法可以限制解压大小:
一、将解压后的数据大小包含在存档中。从压缩文件中读取该值并检查大小是否满足要求。
二、第一种方法并不能完全解决问题,因为解压后的大小可能会被欺骗。所以我们可以传递一个固定大小的缓冲区来进行解压。如果解压过程中数据超出缓冲区边界,则停止并返回失败。
三、另一种方法是流式解压缩。传递压缩数据的小块,解压缩每个块并累积解压缩的大小。如果大小在任何时候超过阈值,则停止并返回失败。