文章详细描述了Maia DAO在一次高危漏洞事件中的应对过程,包括漏洞的发现、资金救援操作、以及后续的改进措施。漏洞导致超过120万美元的资金面临风险,最终通过多方面的合作成功进行了救援。文章还总结了从中学到的教训和改进建议。
该漏洞的严重性被评估为关键。由于救援前面临的资金风险总计超过120万美元,并且涉及Permit2授权,0xfuje被授予最高悬赏100,000美元——以USDC和USDT支付。
我们要特别感谢以下各方的宝贵帮助:
我们也要感谢我们的社区,尽管在救援过程中未能直接提供帮助,但他们在整个期间一直支持着我们,且在行动完成后并未停止支持。
还要特别感谢所有现有工具,使我们的生活更轻松:Alchemy RPC、anvil、foundry、ethers js 等等。在某些新运行上,使用本地 anvil 节点可将后续测试时间缩短至几秒,而某些分叉测试则需要花费多达30分钟。
漏洞的原因是没有重写 CoreBranchRouter
合约中的 callOut
函数。这使得任何人在与 CoreRootRouter
交互时都可以绕过检查,并充当受信任的 CoreBranchRouter:
该问题可以用以下例子解释,其中 RubberDuck 能飞行,因为该函数未被重写:
abstract contract Duck {
bool public quacked;
bool public flown;
function quack() external virtual {
quacked = true;
}
function fly() external virtual {
flown = true;
}
}
contract RubberDuck is Duck {
bool public squeaked;
function quack() external override {
squeaked = true;
}
}
BranchBridgeAgent 在 Branch Chains 中没有任何权限,因此无法直接提取资金。但它可以向 Arbitrum(根链)RootBridgeAgent 合约发送 LayerZero 消息,这允许任何人:
这使得虚拟账户保管下的所有资金以及存入 Branch Chain 的 Ports 都面临风险。为了救援所有资金,我们必须从 Ulysses 中提取所有资金。面临风险的资金有:
Permit2 授权至 VirtualAccounts 也存在风险。但它们将在周一到期,届时将停止受到影响。
我们在知晓漏洞后首要目标是使所有的 Permit2 授权过期,而不引起警觉。过期时间为48小时,我们立即将其减少至30分钟,并需等待2天以使任何未完成的授权过期。两天后,我们又将其进一步减少至5分钟,然后在救援行动开始前的几个小时内完全停止了用户界面。
在用户界面停止后,我们只需稍等以确保没人有旧版在旧标签中。由于用户提取资金可能影响我们从 Branch Ports 提取所有资金的成功,因此在提交交易之前,确保没人进行交互也是很重要的。
如果我们清空了所有 Hermes 和 Maia LP,提取所有 Branch Ports 的余额,索取所有的贿赂,我们就能够挽救所有资金。我们将不得不重新部署所有协议,但可以在显著少于预期的交易中完成,事实就是我们这样做了。
在执行救援交易时,首个解除所有 Branch Ports 的交易耗尽了gas,我们首先尝试切换部署方法或RPC,因为我们正在使用 Arbitrum 的排序器 RPC。我们试图避免拆分交易以防泄漏任何信息,避免被优先竞争。但在多次失败交易后仍没有被优先竞争,同时将合同拆分为部署和执行方式确实奏效,救援行动最终成功。
我们没有任何管理权限,唯一防止重新部署 Ulysses 的方法是在所有链上提交一个为期三周的提案以更新 CoreBranchRouter
。然而,由于在投票期间对资金构成的重大风险,这种方法是不道德的。
新版本已解决这个问题,并在以下提交中添加了额外的安全检查:
在DeFi中,安全性至关重要。编写可读且可审计的代码至关重要,因此隐藏功能应被视为不良实践,尽量避免,即使这会导致额外的代码。我们强烈建议避免使用继承和 Using For
模式,并希望说服其他人也这样做。
正是由于这些实践,漏洞在很长一段时间内未被发现。未能重载/覆盖三个函数使得该问题得以从未被发现,而那些注意到它的人仍然有多个路径可以检查,直到发现问题。因此,使整个ABI和所有代码路径显式化是DeFi中的一项重要实践。我们对代码库的熟悉让我们错过了这一点,并未记录这样的函数不应该可用。
应优先考虑组合,而不是继承。合约的ABI 应在一个地方清晰公开给读者。继承类将导致隐藏代码,特别是它有任何非虚拟的外部函数时。这个漏洞就是一个完美的例子。
继承不应该被完全抛弃。大多数流行库提倡这一做法,因为流行的EIP,我完全理解原因。但仍需谨慎使用,仅在如EIP这样经历极大审查的具体场景中使用。我的观点是,其他情况如Ownable和Reentrancy Guards也同样合适且有用。
库应被使用,但要明确。句法 using A for B
在开头的使用对代码的清晰性往往弊大于利。它确实让代码看起来更简洁,但就像继承一样,它隐藏了功能。这并不是漏洞的原因,但可能会导致类似的状况。
我不认为始终这样做有充分理由。虽然它可能减少代码的大小并显著提高算数操作的可读性,但除此之外,我只看到采用这一做法会增加代码混淆度。
清空某些具有宽范围位置的Uniswap V3 Pools代价高昂,且会导致使用过多的gas。因此我们留下了非常小的Arbitrum代币。这导致了几美元的USDC和USDT的微不足道的稳定币损失。由于大多数池与ETH配对,尤其是不稳定,损失更大达1.78 ETH。
损失的ETH已从Maia DAO的国库中扣除,以使每个人得到补偿。还有来自非合作协议的未索赔费用和贿赂。除了主网中的51 USDC和0.003 ETH外,每笔金额都低于10美元。由于数额较小,这些将保留在开发者地址中,并用于测试目的。
将会有来自虚拟化代币的未索赔费用和贿赂,这些将被退还给原协议或再次作为贿赂存入他们的测量中:
为了全面披露,仍有总共27.073967 USDC和11.784153 USDT因投票的贿赂而面临风险。我们没有索赔,因其低额在多个用户之间分配。
你是否厌倦了在多个链上必须拥有流动性?那么,你可以在Arbitrum中无需许可地向Uniswap V3添加流动性,并立即允许你的用户在所有链上进行交易。这更加节省成本,并让你对流动性有更多控制权。
一切都是非保管式的且无需许可。所有影响协议安全的重大变化都需要通过治理合约执行,执行至少需要三周的时间,并且有一个拥有否决权的多签以防止治理攻击。
所有日期和时间均为UTC。
09/20 19:54 — 漏洞赏金由 0xfuje 提交。
09/20 20:11 — 漏洞赏金由 Immunefi 升级审核。
09/20 22:05 — 漏洞赏金确认。
09/20 22:11 — 联系 SEAL 911。
09/20 22:55 — 用户界面中的 Permit2 遴选时间从48小时减少至30分钟。
09/20 23:40 — 请求SEAL 911核实计划和其他问题。
在周末,我们获取了所有必要信息,同时开发并手动测试了所有必要的救援合约,同时耐心等待最后的Permit2过期结束。我们最初的计划是避免整个协议的重新部署,但最后发现这不可行。
09/23 12:15 — 联系SEAL 911,更新状态和更多问题。 09/23 13:06 — 他们建议我们与先前审核过的审计员进行资格验审。
09/23 13:19 — 我们联系 Zellic,他们也是SEAL 911的一部分,并免费提供服务和更多帮助。
09/23 13:44 — SEAL 911创建了战情室。
09/23 14:02 — 来自 Arbitrum 团队的 gzeon 告诉我们关于Arbitrum特定的gas限制和容量。在最佳情况下,我们需要135笔交易,每笔交易耗时30M gas,总费用将超过40亿gas。Arbitrum有L2 gas限制为32M gas,通常每秒处理超过5M gas,最大可处理到7M gas/s,之后gas会开始上升。因此我们的初步计划是不可行的,我们必须要么不拯救某些资金,要么寻找其他备用方案。
09/23 14:02 — 我们开始与Zellic进行电话联系。他们正在重新审核协议,寻找可能被遗漏的一切,以帮助改善救援行动。他们还在寻找以更少交易保存资金的替代路径。
09/23 14:26 — 来自Zellic的 Jazzy 提出了最终成为最佳选项的解决方案,空出与WETH配对的Maia和Hermes Uniswap V3 Pools,我们需要重新部署所有协议,但可以在两笔交易中拯救绝大多数资金。
在接下来的四小时内,我们对脚本进行了必要的更改并进行了彻底测试。Zellic继续寻找其他最佳方式来处理这种情况,超出他们建议的解决方案,并尝试寻找任何其他漏洞并审计救援合约。
09/23 16:59 — 用户界面中的 Permit2 过期时间减少至5分钟。
09/23 18:06 — 我们正在所有链上部署多重签名以发送资金,并准备通告。
09/23 18:42 — 移除了外部CSP策略,使用户界面无法使用,允许最后的授权过期。
09/23 18:55 — 我们已经准备好提取最后的calldata,测试并开始救援。
由于foundry分叉测试和现场Arbitrum环境之间gas值不一致,Branch Port救援交易耗尽了gas。
09/23 20:34 — Branch Port 救援交易因耗尽gas而失败。
09/23 20:36 — 首个Maia和Hermes LP交易成功。
09/23 20:56 — Branch Port 救援交易因耗尽gas而失败。
09/23 20:58 — Branch Port 救援交易因耗尽gas而失败。
09/23 21:01 — Branch Port 救援交易因耗尽gas而失败。
09/23 21:12 — Branch Port 救援交易因耗尽gas而失败。
09/23 21:21 — 首个Branch Port救援交易成功。确保大多数资金安全。
09/23 21:25 — 宣布并分享救援行动。
09/23 21:41 — 编辑最终Twitter公告,包含正确的Twitter地址:https://x.com/MaiaDAOEco/status/1838333005107986647
09/23 21:46— 与Zellic的通话和战情室结束。
由于两个成功交易之间存在延迟,我们只能在两笔成功交易后公布这是一次救援行动,因此有些用户在不知情的情况下购买了我们的代币。为了使这些用户得到补偿,我们制定了一个计划以恢复这些操作,并尽可能筹集资金再次清空Maia和Hermes LP。更多信息可在下面的Twitter公告和 这里 查看。
09/24 01:17 — 第二个Maia和Hermes LP交易成功。
09/24 01:17 — 宣布并分享第二个快照,以减轻用户在系统被暂停后的操作:https://x.com/MaiaDAOEco/status/1838387231267889488
09/24 02:26— 第二个Branch Port救援交易成功。
10/11 02:03 — 完全补偿所有代币,除了Maia/hermes/bHermes。
10/17 02:45 — 完全补偿Maia/hermes/bHermes。
10/24 15:36 — 为用户从先前部署中索赔Arbitrum的ETH贿赂。
10/24 18:24 — 最终救援交易,清空存放在Branch Ports中的所有代币(仍有流动性低的meme代币存放着,我们与创作者保持联系)。
Branch Port:
Maia 和 Hermes LP:
贿赂:
失败的Branch Port:
所有用户已得到完全补偿。金额已空投到他们的地址。
有关补偿的更详细信息,可在以下Github库中找到:
- https://github.com/Maia-DAO/reimbursement
网站 || Twitter || Discord || 博客
- 原文链接: medium.com/@maiaDAO/post...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!