本次攻击如何发生?
封面:Photo by Mika Baumeister on Unsplash
2023 年 4 月 5 日,据 Beosin-Eagle Eye 态势感知平台消息,Arbitrum 链的 Sentiment 项目遭受黑客攻击,黑客获利约 1 百万美元,不过在项目方的 “感化” 下,黑客已退还大部分资金。
据了解,Sentiment 是一个 Defi 借贷项目。借贷逻辑是 Sentiment 为用户开设账户,用户通过此账户将资产质押到其他项目获取收益,并且在质押之后也可以进行借贷。本次攻击如何发生,请听我们详细分解。
事件相关信息
攻击交易
0xa9ff2b587e2741575daf893864710a5cbb44bb64ccdc487a100fa20741e0f74d
攻击者地址
0xdd0cdb4c3b887bc533957bc32463977e432e49c3
攻击合约
0x9f626f5941fafe0a5b839907d77fbbd5d0dea9d0
被攻击合约
0x62c5aa8277e49b3ead43dc67453ec91dc6826403
攻击流程
跟着 Beosin 一起来看看黑客是如何对 Sentiment 项目 “下手” 的。
1. 攻击者首先从 Tornado.Cash 提取 1ETH 到其以太坊账户,然后通过跨链桥将 0.7ETH 转入到其 Arbitrum 账户做好 gas 准备。然后部署了他的攻击合约。
2. 接着攻击者发起攻击交易,首先攻击合约通过闪电贷获得 606 个 WBTC,10050 个 WETH 和 18,000,000 个 USDC。然后,攻击合约在 Sentiment 项目中按照项目方的正常业务逻辑创建账户,并只在账户中质押了 50 个 WETH,再通过 Sentiment 项目的 AccountManager 合约将这 50 个 WETH 添加到 Balancer 项目的 Balancer: Vault 合约中。为价格操控之后的借款提供质押记录。
3. 可以看到,上面的质押是通过 Sentiment 开设的账户进行的,之后攻击合约以合约身份向 Balancer 提供大量流动性,一共花费了 606 个 WBTC,10000 个 WETH 和 18,000,000 个 USDC。
4. 紧接着攻击者调用 Balancer: Vault.exitPool() 函数取回流动性,这个过程中 Balancer: Vault 会向攻击者转账从而调用攻击者预先构造好的 fallback 函数。
5. 在 fallback 函数中,攻击者调用了 Sentiment 项目的 borrow 进行借贷。按照借贷的逻辑,借款需要检查借款人的质押,而 Sentiment 确实通过 RiskEngine.isAccountHealthy() 进行了检查。
6. 这里的检查是通过 WeightedBalancerLPOracle.getPrice() 函数获取抵押物价格,而该函数依赖 Balancer: Vault.getPoolTokens() 返回的数据。由于这时攻击者退出流动性的操作还没有完成,这里返回的数据是包含了攻击者流动性的旧数据。
7. 这个旧数据导致了预言机的价格计算错误,WeightedBalancerLPOracle.getPrice() 返回价格由正常的 220132731298820699 变成了 3550073070005057760,提升了约 16 倍,使得攻击者抵押的 50WETH 价值升高,从而能取出更多资产。
8. 攻击者通过多个 borrow 和 exec,最终获取了 0.5 个 WBTC、30 个 WETH、538,399USDC 和 360,000USDT。然后通过跨链回到了攻击者的以太坊地址,并全部换成了 517 个 ETH,价值约 1 百万美元。
漏洞分析
根据 Beosin 安全团队的分析,本次攻击主要利用了重入导致的价值计算错误。这一漏洞的主要原因是 Sentiment 项目未考虑到 Balancer: Vault._joinOrExit() 函数在流动性移除时,先转移资产再更新池子余额。导致在转移 ETH 资产时, 会触发攻击合约的 fallback 调用,此时攻击合约借款,Sentiment 采用未更新的 Balancer 的数据计算价格,导致比质押更多的资产被借出。
Sentiment 的价格计算函数:
Balancer 项目的流动性添加与移除函数:
资金追踪
Beosin Trace 追踪发现,被盗资金为 517 个 ETH,经项目方与黑客协商,其中 51ETH 为赏金已被黑客转移到 Tornado.Cash,其余资金已归还项目方。
总结
针对本次事件,Beosin 安全团队建议:
1. 在合约开发时,采用检查-影响-交互模式。并且在多个合约相互依赖时需要更加注意合约之间的影响带来的安全问题。
2. 项目与其他项目有数据依赖时,需要谨慎考虑项目之间的结合是否会导致新的安全问题出现。尽量通过原始数据作为自己逻辑的参考,减少对其他项目运算结果的依赖。