事后分析(2024年9月)

  • maiaDAO
  • 发布于 2024-11-02 17:38
  • 阅读 46

文章详细描述了Maia DAO在一次高危漏洞事件中的应对过程,包括漏洞的发现、资金救援操作、以及后续的改进措施。漏洞导致超过120万美元的资金面临风险,最终通过多方面的合作成功进行了救援。文章还总结了从中学到的教训和改进建议。

该漏洞的严重性被评估为关键。由于救援前面临的资金风险总计超过120万美元,并且涉及Permit2授权,0xfuje被授予最高悬赏100,000美元——以USDC和USDT支付。

我们要特别感谢以下各方的宝贵帮助:

  • 0xfuje 发现了这个漏洞。
  • 来自 SEAL 911 的 pcaversaccio 在几分钟内为我们联系到任何需要的人。
  • 来自 Zellic 的 Jazzy 提出了理想的救援方案,并在登机前的8小时内保持待命。
  • 来自 Arbitrum 的 gzeon 为我们提供了链可以支持的宝贵见解。
  • Immunefi 提供了提交的平台。
  • SEAL 911 提供支持和建议。
  • Zellic 的技能和专业知识。
  • Arbitrum 团队确保我们的操作会成功。
  • 所有参与其中并帮助我们成功执行这次白帽黑客行动的人员。

我们也要感谢我们的社区,尽管在救援过程中未能直接提供帮助,但他们在整个期间一直支持着我们,且在行动完成后并未停止支持。

还要特别感谢所有现有工具,使我们的生活更轻松:Alchemy RPC、anvil、foundry、ethers js 等等。在某些新运行上,使用本地 anvil 节点可将后续测试时间缩短至几秒,而某些分叉测试则需要花费多达30分钟。

救援行动

漏洞的原因是没有重写 CoreBranchRouter 合约中的 callOut 函数。这使得任何人在与 CoreRootRouter 交互时都可以绕过检查,并充当受信任的 CoreBranchRouter:

  • 将任何地址添加为 RootBridgeAgent 的 BranchBridgeAgent。
  • 从 Branch Ports 提取全球代币。

该问题可以用以下例子解释,其中 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 消息,这允许任何人:

  • 假装进行桥接交易并随意铸造全球代币。这可以被“真正的” BranchBridgeAgent 提取。
  • 假装进行已签名的桥接交易并管理任何用户的虚拟账户。

这使得虚拟账户保管下的所有资金以及存入 Branch Chain 的 Ports 都面临风险。为了救援所有资金,我们必须从 Ulysses 中提取所有资金。面临风险的资金有:

  • 存入 Branch Chain 的 Ports 中的代币(每条链除了 Arbitrum)。
  • 使用虚拟账户的质押LP。
  • 质押LP的奖励。
  • 使用虚拟账户进行投票的费用/贿赂。
  • 委托给虚拟账户的投票/测量/提升力量。
  • 在虚拟账户中解包的 BurntHermes 和 VoteMaia。
  • 空投给成千上万虚拟账户的 BurntHermes、Maia 和 Hermes。
  • 来自失败的跨链消息的代币。
  • Arbitrum 的 WETH/USDC/USDT 与跨链或统一流动性代币配对的 Uniswap V3 Pools。

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也同样合适且有用。

使用 For

库应被使用,但要明确。句法 using A for B 在开头的使用对代码的清晰性往往弊大于利。它确实让代码看起来更简洁,但就像继承一样,它隐藏了功能。这并不是漏洞的原因,但可能会导致类似的状况。

我不认为始终这样做有充分理由。虽然它可能减少代码的大小并显著提高算数操作的可读性,但除此之外,我只看到采用这一做法会增加代码混淆度。

关键改进/经验教训

  • 避免在 Solidity 中使用继承和“Using For”。
  • 确保准备好支持这种情况的信息,例如每个用户的所有余额(尽可能细分——从本金到收益)。
  • 有一个脚本用于清空平台的所有余额。你无法预测漏洞(否则你就会修复它),但拥有一个脚本会有帮助。例如,它可以索取所有奖励,转移和/或提取用户的所有余额。对其的准备在时间紧迫时可能会非常有用。在关键时刻获取功能选择器或接口可以通过这一步轻松躲开。另一个例子是拥有一个清空 Uniswap V3 Pools 的脚本。希望你只是提高安全性并改善睡眠,而不会用到它。在我们这里,对 balancer 添加流动性的测试和脚本帮助了很多。
  • 保持一个更新的救援计划。简单的操作如获取余额、制作脚本、测试脚本、运行脚本将确保你在整个操作中不忘记任何事情且保持进度。
  • 使用较短的Expired Permit2。Uniswap有30天的Permit2过期时间,我们最初将其设为2天,但现在进一步减少到1小时。虽然对我们用户界面的私有代码库的更新不会引发任何警报,但如果它公开并如我们所希望的尽快上线,这样的变化可能会引发关注。虽然让用户设置是理想的,但预设不应如此庞大,除非你是Uniswap。等待48小时才能执行救援行动并不愉快。但使用Permit2是极其有价值的,确保没有拖延的未完成授权,这次救援行动否则会更加困难。
  • 在分叉测试中避免接近gas极限。在我们的本地anvil分叉中,我们的交易gas预计为30M,因此认为是安全的。我们本该留出更大的余地,这会节省金钱并改善沟通。
  • 避免智能合约风险。在使用虚拟账户存入资金时,我们优先考虑用户体验而非安全性。我们已经在用户界面中进行了更改以防止这种情况,现在虚拟账户仅作为个人多次调用。我们还计划用要求用户签名才能执行交易的Gnosis Safe Multisig等更安全的解决方案来替代他们。
  • 保持警惕。在任何时候都要避免不必要的风险。使用私有RPC,使用Flashbots以及排序器RPC提交交易。如有可能,进行原子救援交易。除非极其必要,否则不要与任何人分享任何信息。

总计

清空某些具有宽范围位置的Uniswap V3 Pools代价高昂,且会导致使用过多的gas。因此我们留下了非常小的Arbitrum代币。这导致了几美元的USDC和USDT的微不足道的稳定币损失。由于大多数池与ETH配对,尤其是不稳定,损失更大达1.78 ETH。

损失的ETH已从Maia DAO的国库中扣除,以使每个人得到补偿。还有来自非合作协议的未索赔费用和贿赂。除了主网中的51 USDC和0.003 ETH外,每笔金额都低于10美元。由于数额较小,这些将保留在开发者地址中,并用于测试目的。

将会有来自虚拟化代币的未索赔费用和贿赂,这些将被退还给原协议或再次作为贿赂存入他们的测量中:

  • 1109 ARA
  • 4500 aHERMES
  • 1.46 aMAIA
  • 287 GAMEFI

为了全面披露,仍有总共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:

  1. 0x29fe0d0ced66f535c540e592160541e9bf4283a31b530b1d8dbef7f3a718a55b
  2. 0x075bbee275315c5a249cab926550d84d0a4e9ddfd7776eccf4de2a479becf178
  3. 0x035650185b92df79e8098ae070d645a98506fa8b2925b5295cb42535dcb387f8

Maia 和 Hermes LP:

  1. 0x81717ee5b15792fdbf8d8013f83446901cae9aa96f394f2c9dabeb34be4e1f50
  2. 0xc84ec5128605f4903dcb0b90715ee84ca8d764c8ffb9bf7b3fe4bcade2e51e57

贿赂:

  1. 0xda16e80ce74e3f8af3a38a48e7dbd386fb6761f9210ceee12745b314511c278c
  2. 0x26fefee587dad7bf496df9adbf51eebcac5533d7406e41a5c88d8ccd0f7b717b
  3. 0x686ef81546c090e39242d28f5351afb7e080cbf0822b40480dad0c547fb302ae
  4. 0x7e65d275abe3a2885144bb06efcd356081446501e879c1f94dcfe1b6b5ea7779
  5. 0x7be573d0c79638eff78da10a976e922abf10f1218e17322f7891323856360110
  6. 0xc6feb069acb6f50252f972d98cfbdb3420e29e170066ce5b59c5be4187160da5
  7. 0x34c3418f9b6204810c0ba7ce516ddb0f914e742fea4cc1504dcdf74851ce8761
  8. 0x0265083502d9dd6d4d8a2d9616f9204aebcd01e6cc322ddd163051773c126b15

失败的Branch Port:

  1. 0xe410a2f6901f10ca0c44392e1b417583108ed8c9e8c5230ba4d36399d3cfa06e
  2. 0x5ebe38130f2059efbed7c48cc7ae7651151a6f0ded808f52702dbba556367117
  3. 0xc76d34ed3b688a8c36bee271e7ccdbda48d780ba5001dc013340327ee87acdcc
  4. 0x38d1424201128aabe6cecbd4fde2ae8f17062356ffc89e643969406106485a73
  5. 0x2750be34ff14188d03b53e5fe03218741247e4f98a41de828adbaa64cf4bfcf7

补偿

所有用户已得到完全补偿。金额已空投到他们的地址。

有关补偿的更详细信息,可在以下Github库中找到:

- https://github.com/Maia-DAO/reimbursement

加入Maia DAO👇

网站 || Twitter || Discord || 博客

  • 原文链接: medium.com/@maiaDAO/post...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
maiaDAO
maiaDAO
江湖只有他的大名,没有他的介绍。