Ronin Bridge 出现异常提取跨链资产行为根本原因分析
8月6日,Ronin Bridge项目出现异常,大量跨链资产被提取。经过安全团队分析,问题出在合约升级环节——项目方在升级合约时,未正常初始化配置跨链交易确认所需的operator权重,导致合约中的minimumVoteWeight参数为零,结果就是,任何人的签名都能通过跨链验证。
攻击交易链接:
https://etherscan.io/tx/0x2619570088683e6cc3a38d93c3d98899e5783864e15525d5f5810c11189ba6cb

安全团队正与项目方合作处理此事。本文从技术层面拆解这起事件的关键细节,先看交易中的两个异常点:
首先,提取金额踩在了跨链提取额度的上限。Ronin Bridge中,WETH的限制额度为4000,而此笔交易恰好提取了3996个。虽然这本身不是漏洞,但已经足以引起警惕。其次,更蹊跷的是,这笔交易的跨链验证者Operator只有一个,且对应的操作权重为0。在Ronin Bridge中,用户提取跨链资产需要多个Operator签名,且累计权重必须达到指定阈值,这里显然不满足条件。
事件分析
结合链上数据和合约分析发现,Operator由Manager合约独立管理,这是一个治理合约,专门用于管理Ronin Bridge。查看其交易记录,最近一笔交易就在异常交易之前,是对Ronin Bridge合约进行升级。漏洞方向由此明确——问题出在合约升级上。

进一步对比升级前后的代码,发现事件使用的关键参数“_totalOperatorWeight”是本次升级新增的,需要在升级中调用initializeV3函数进行初始化,以设置V3版本新增的“OperatorWeight”。但遗憾的是,升级合约的交易中并未调用initializeV3,而是错误调用了initializeV4函数,进行了V4版本的初始化。


至此,漏洞原理已经清晰:Ronin Bridge项目方在合约升级时未正确对新增数据进行初始化,导致关键数据“_totalOperatorWeight”始终为0,使得任意用户的提取请求都能通过审核。
好消息是,项目方已确认这是白帽行为,并已退款,未造成过多损失。但这也暴露了合约升级这一高风险环节——稍有不慎,就可能出现致命漏洞。
可升级合约
可升级合约是一种solidity智能合约设计方案,允许已部署的合约在不完全重新部署的情况下进行升级或修改。核心思路是将合约逻辑和数据分离,利用“delegatecall”调用逻辑合约。

尽管这种模式提供了灵活的升级能力,但安全风险必须高度重视。袋里合约作为合约系统的入口点,任何对它的攻击都可能影响整个合约的安全性。在设计和部署可升级合约时,以下几点需要特别关注:
函数选择器冲突
在以太坊虚拟机中,每个函数都有唯一的函数选择器,由函数签名前4个字节的哈希值决定。在袋里模式中,调用函数时,合约会先检查袋里合约中是否有匹配的函数接口,如果没有,才通过fallback中的delegatecall调用逻辑合约。如果袋里合约和逻辑合约中存在相同的函数选择器,调用就会直接落在袋里合约上,而不是逻辑合约,这可能导致预期外的行为,甚至安全漏洞。
存储冲突
在EVM中,合约的状态数据存储在特定的存储槽中,每个存储槽由索引确定。在袋里模式中,存储槽通常由袋里合约管理,逻辑合约通过袋里合约访问。如果逻辑合约新增的状态变量与袋里合约现有的存储槽冲突,就可能导致数据被覆盖或混乱。
合约初始化问题
每次升级都可能涉及变量的更改和新增,因此必须确保正确设置关键变量。Ronin Bridge事件正是这个问题的最佳注脚——初始化没做好,直接导致被攻击。此外,还需要确保初始化函数只能被调用一次,防止被恶意攻击者重复调用修改关键变量。
delegatecall调用机制
delegatecall是一种低级调用机制,允许一个合约在其上下文中执行另一个合约的代码。这意味着调用合约的存储、地址和消息发送者保持不变,但执行的逻辑来自被调用的目标合约。如果目标合约地址不存在,delegatecall执行会失败并返回失败码,但这种失败可能不会被立刻发现,导致调用看起来成功但实际操作未生效,从而引发系统不一致或错误。
袋里合约权限管理
袋里模式通过分离袋里合约和逻辑合约的职责,使得合约可以在不改变数据存储的情况下进行升级,但同时也引入了复杂的权限管理问题。正确地管理权限对于保证合约系统的安全性和稳定性至关重要。