亿次空耗 - The Trail of Bits博客
在以太坊强大的区块链技术背后,隐藏着一个开发者们较少提及的挑战:编写健壮的以太坊ABI(应用二进制接口)解析器的复杂性。以太坊ABI对区块链基础设施至关重要,它实现了智能合约与外部应用间的无缝交互。数据类型的复杂性和编解码的精确需求使得ABI解析充满挑战。规范或实现中的模糊性可能导致危及用户的漏洞。
本文将深入剖析一个新型漏洞,该漏洞针对这些解析器,令人联想到曾肆虐XML领域的"十亿大笑"攻击。我们发现以太坊ABI规范部分条款表述松散,导致多个实现存在可利用漏洞,包括eth_abi(Python)、ethabi(Rust)、alloy-rs和ethereumjs-abi等库,可能引发拒绝服务(DoS)攻击。截至发文时,仅Python库已完成修复。
什么是以太坊ABI?
当链上合约交互或链下组件与合约通信时,以太坊使用ABI编码来处理请求和响应。这种编码方式不自我描述,编解码器需要提供定义数据类型的模式。与C语言中平台相关的ABI不同,以太坊规范了应用间二进制数据的传递方式。虽然规范尚未正式化,但清晰展现了数据交换机制。
当前规范存在于Solidity文档中,其类型定义影响了Solidity和Vyper等智能合约语言。
漏洞原理
零大小类型(ZST)指在磁盘存储中占零(或极小)字节,但加载到内存后需要显著更多空间的数据类型。以太坊ABI允许ZST存在,攻击者可利用其特性,通过极小量的磁盘或网络数据诱发巨大的内存分配。
考虑以下场景:当解析器遇到ZST数组时会发生什么?它会尝试解析数组声明包含的所有ZST元素。由于每个元素占零字节,定义超大型ZST数组轻而易举。
示例显示:20字节的磁盘数据可反序列化为包含数字2、1、3的数组;而仅8字节的数据却能反序列化为2³²个ZST元素(如空元组或空数组)。如果每个ZST在解析后仍占零字节内存,这不成问题。但实践中,每个元素通常需要少量非零内存,导致整体数组需要巨大内存分配,形成DoS攻击。
概念验证
我们通过以下十六进制数据载荷演示多个库中的漏洞:
|
|
该载荷包含两个32字节块,描述序列化的ZST数组。第一块定义数组元素偏移量,第二块定义数组长度。我们使用()[ ]
和uint32[0][]
两种ABI模式进行解码测试。
eth_abi (Python)
|
|
4.2.0版本前的官方库会先挂起,最终因内存不足终止。
ethabi (Rust)
v18.0.0版本可通过CLI直接触发漏洞:
|
|
ethers-rs (Rust)
v2.0.10版本通过Vec<[u32; 0]>
类型隐式使用漏洞模式:
|
|
foundry (Rust)
该工具集基于ethers-rs,同样存在漏洞。可通过CLI或部署恶意合约触发:
|
|
alloy-rs
0.4.2版本的动态ABI解析同样存在挂起问题:
|
|
ethereumjs-abi
0.6.8版本的JavaScript实现也受影响:
|
|
漏洞发现与利用
该漏洞的发现源于对borsh-rs库中类似问题的研究。最新版Solidity和Vyper已禁止ZST定义,因此难以在主网部署触发此漏洞的智能合约。但任何使用受影响库解析不可信ABI声明的应用(如Etherscan)都可能面临风险。
协调披露
我们遵循以下时间线进行披露:
- 2023年6月30日:首次联系各库维护者
- 2023年8月2日:为eth_abi创建GitHub安全公告
- 2023年8月31日:eth_abi发布无公开说明的修复
- 2023年12月29日:公开发布分析报告并创建GitHub问题
建议开发者通过模糊测试强化解码器安全性,我们正将相关测试用例纳入OSS-fuzz持续检测体系。