可升级合约漏洞—Web3音乐平台Audius被黑事件分析
2022年7月24日消息,黑客从音乐流媒体协议Audius转移了1800万个AUDIO代币,损失约600万美元。
SharkTeam第一时间对此事件进行了技术分析,并总结了安全防范手段,希望后续项目可以引以为戒,共筑区块链行业的安全防线。
攻击者发起攻击交易如下:
攻击者发起了两次攻击,其中第一次攻击失败,第二次攻击成功。
攻击者地址:0xa0c7bd318d69424603cbf91e9969870f21b8ab4c
第一次攻击如下:
攻击合约:0xa62c3ced6906b188a4d4a3c981b79f2aabf2107f
攻击交易:0x3bbb15f9852c389e8d77399fe88b49b042d0f22aad4a33c979fbabc60a34b24f
交易中提交了84号提案
在提案并没有投票,所以也没有被执行,但该交易通过调用Governance合约的初始化函数并对提案82和83进行评估,修改了其状态:
接下来,我们以第二次攻击发起的交易来说明整个攻击的过程。
攻击合约地址:0xbdbb5945f252bc3466a319cdcc3ee8056bf2e569,简记为0xbdbb
攻击者在创建了攻击合约后,通过攻击合约发起了攻击,包含4笔交易:
交易1 txHash: 0xfefd829e246002a8fd061eede7501bccb6e244a9aacea0ebceaecef5d877a984
该交易包含了7个 步骤,如下:
1. 通过Audius的代理合约对Governance合约进行初始化
初始化函数如下:
该初始化函数对投票系统进行初始化,变量说明以及参数设置如下:
(1)registryAddress:注册代理合约地址,设置为攻击合约地址;
(2)votingPeriod:治理提案开放投票的区块周期,设置为3,即3个区块内可以投票,第4个及其之后的区块不可以再进行投票;
(3)exectionDelay:投票期结束后必须通过的区块数才能评估/执行提案,设置为1,即投票期结束后再经过1个区块才能执行,或者说结束投票后需等待1个区块才可以评估/执行提案;
(4)votingQuorumPercent:投票认为提案有效所需的总股份的最低百分比,设置为1;
(5)maxInProgressProposals:一次性可能处于InProgress状态的最大提案数量,设置为4;
(6)guardianAddress:具有特殊治理权限的账户地址,设置为攻击合约。
之所以如此设置参数,是为了让提案可以不需要复杂的多方投票就可以进行执行。
2. 评估/执行84号提案,即第一次攻击提交的提案,评估为QuorumNotMet
原因是没有投票。
3. 查询Governance代理合约的AUDIO代币余额
4. 提交85号提案,与84号提案相同。该提案的功能就是将Governance代理合约中的AUDIO转移到攻击合约,数量不能超过Governance代理合约的AUDIO余额。
5. 通过代理合约对Staking合约进行初始化
将治理代币地址以及代理合约地址都设置为攻击合约。
6. 通过代理合约对DelegateManagerV2合约进行初始化
将治理代币地址以及代理治理地址都设置为攻击合约。
7. 通过代理合约将DelegateManagerV2合约中的服务提供者工厂合约为攻击合约
8. 通过代理合约将质押的代理权授权给攻击合约,授权的代币数量为1e31
通过以上步骤,攻击者篡改并获取了治理系统的最高权限。
然后攻击者在提交了提案85之后的3个区块内进行了投票,即第2笔交易:
交易2 txHash: 0x3c09c6306b67737227edc24c663462d870e7c2bf39e9ab66877a980c900dd5d5
投票完成后,攻击者在3个区块的投票期以及1个区块的等待期之后对提案85进行了评估/执行,即第3笔交易:
交易3 txHash: 0x4227bca8ed4b8915c7eec0e14ad3748a88c4371d4176e716e8007249b9980dc9
执行提案85,将18,56万枚AUDIO转移到攻击合约。
攻击合约收到1800万枚AUDIO后,将其兑换为704 枚ETH,即第4笔交易:
交易4 txHash: 0x82fc23992c7433fffad0e28a1b8d11211dc4377de83e88088d79f24f4a3f28b3
最后,攻击者将兑换的ETH存入到了Tornash平台:
从以上攻击过程中,我们发现攻击者之所以能够攻击成功,根本原因在于通过代理合约多次调用初始化函数,而初始化函数本应该只能调用一次的。以Governance合约中的初始化函数为例,其代码如下:
这里使用了Openzeppelin里面的初始化器initializer,代码如下:
明显,initializer没有起到任何作用,原因在于代理调用。这里实现合约中定义的两个bool类型的状态变量initialized和intializing分别占用了存储插槽slot0中的前16个字节。第1个8字节为initialized,第2个8字节为initializing。由于代理合约本身定义了一个地址类型的状态变量proxyAdmin,其值为0x80ab62886eacfebca74511823d4699eb88fd097e,同样占用了存储插槽slot0,第1个8字节为0,第2个8字节为0x80ab6288,第3个8字节为0x6eacfebca7451182,第4个8字节为0x3d4699eb88fd097e.
于是,实现合约中的initialized和intializing与代理合约中的proxyAdmin同时占用了存储插槽slot0,从而引起了存储冲突。
插槽0中的数据分布如下:
初始化函数执行到initializer时,从存储插槽slot0的第1个8字节读取initialized,其值为0,即false;从存储插槽slot0的第2个8字节读取initializing,其值为0x80ab6288 > 0,即true。
因此,初始化器initializer完全没有任何作用。
引发本次攻击事件的根本原因是可升级合约的实现过程中,代理合约与实现合约的存储出现冲突,导致初始化器initializer完全失去了作用。针对该漏洞,我们建议在使用可升级合约时首先要熟悉代理模式,对合约数据和逻辑做好规划,避免出现数据冲突、丢失等问题。另外,选择多个智能合约审计团队进行多轮审计,也是提高合约安全性的重要保障。
关于我们
SharkTeam的愿景是全面保护Web3世界的安全。团队成员分布在北京、南京、苏州、硅谷,由来自世界各地的经验丰富的安全专业人士和高级研究人员组成,精通区块链和智能合约的底层理论,提供包括智能合约审计、链上分析、应急响应等服务。已与区块链生态系统各个领域的关键参与者,如Huobi Global、OKC、polygon、Polkadot、imToken、ChainIDE等建立长期合作关系。
Web:https://www.sharkteam.org
Telegram:https://t.me/sharkteamorg
Twitter:https://twitter.com/sharkteamorg
Reddit:https://www.reddit.com/r/sharkteamorg
更多区块链安全咨询与分析,点击下方链接查看
D查查|链上风险核查 https://m.chainaegis.com