Skip to content
On this page

深入虎穴,夺回万千 DVT 币:Climber 挑战的攻防之道

在区块链安全的世界里,每一次的漏洞挖掘都像是一场惊心动魄的寻宝之旅。今天,我们将带你走进 Damn Vulnerable DeFi v4 的“Climber”挑战,揭秘如何巧妙地从一个戒备森严的 DVT 代币金库中,安全地转移全部资产。

挑战背景:层层设防的 DVT 金库

这次的挑战围绕着一个名为 ClimberVault 的智能合约展开,它是一个可升级的合约,遵循 UUPS 模式,并且守护着高达 1000 万枚 DVT 代币。这个金库的“主人”并非普通账户,而是一个 ClimberTimelock 合约。更添一层安全防护的是,这个 Timelock 合约拥有一个“Proposer”角色,负责安排在 1 小时后执行的操作。而金库本身,则额外拥有一个“Sweeper”角色,能够在紧急情况下“扫荡”所有代币。

我们的目标十分明确:解救金库中的所有 DVT 代币,并将它们安全地转移到指定的恢复账户。

攻破防线的关键:理解漏洞与设计

要成功完成挑战,我们需要深入理解各个合约的设计以及它们之间的交互。

  1. ClimberVault (金库合约)

    • UUPS 升级模式: 这意味着金库的逻辑可以被替换,而存储的地址(proxy 地址)保持不变。
    • withdraw 函数: 只有合约的 owner(即 Timelock)才能调用,并且有提款金额 (WITHDRAWAL_LIMIT) 和时间间隔 (WAITING_PERIOD) 的限制。
    • sweepFunds 函数: 只有 sweeper 角色可以调用,可以一次性转移金库中所有的代币。
    • 所有权转移:initialize 函数中,金库的所有权被转移给了 ClimberTimelock 合约。
  2. ClimberTimelock (时间锁合约)

    • PROPOSER_ROLE 只有拥有此角色的账户才能通过 schedule 函数安排操作。
    • schedule 函数: 安排一个操作,该操作将在 delay(默认为 1 小时)后才能被执行。
    • execute 函数: 任何人都可以调用,执行已经安排且达到执行时间的 schedule 操作。
    • updateDelay 函数: 只有合约自身(address(this))才能调用,用于更新 delay
  3. ClimberConstants.sol & ClimberErrors.sol: 提供合约的常量和错误定义,是理解合约行为的重要参考。

策略制定:步步为营,化解危机

面对如此严密的防护,我们需要一个多步走的策略:

  • 第一步:打破 Timelock 的时间锁限制。 金库的 withdraw 函数受制于 15 天的等待期,而且提款额度有限。而 ClimberTimelockdelay 只有 1 小时。如果我们能够将 Timelock 的 delay 设置为 0,那么 scheduleexecute 的时间差就会消失,操作可以近乎实时地执行。

  • 第二步:夺取 Timelock 的 PROPOSER_ROLE 金库的 sweepFunds 功能是直接转移所有代币的终极手段,但只有 sweeper 才能调用。而 ClimberTimelockowner 是 Timelock 本身,可以通过 Timelock 的 execute 函数来执行。为了能让我们的合约(或玩家账户)来触发 sweepFunds,我们需要获得 Timelock 的 PROPOSER_ROLE,这样我们才能通过 Timelock 来升级金库合约,并为 sweepFunds 设置我们控制的 sweeper

  • 第三步:升级金库,指定“我们”为 Sweeper。 Timelock 合约是金库的 owner,并且 Timelock 的 owner 是其自身 (address(this))。 Timelock 的 ADMIN_ROLEdeployer 和 Timelock 自己持有。 ADMIN_ROLE 可以授予 PROPOSER_ROLE。 通过 ClimberTimelockexecute 函数,我们可以:

    1. 将 Timelock 的 delay 设置为 0。
    2. PROPOSER_ROLE 授予我们的合约(MY)。
    3. 然后,我们可以利用 Timelock 的 PROPOSER_ROLE 来安排一个升级金库的操作,将金库的实现替换成一个我们自己编写的,能够设置 sweeper 的合约 (YourContract)。
  • 第四步:执行 Sweeper 角色,转移代币。 一旦金库被升级为 YourContract,并且 YourContractsetSweeper 函数被调用,将我们的玩家地址设置为 sweeper,那么我们就可以直接调用 ClimberVaultsweepFunds 函数,将所有 DVT 代币转移到预设的恢复账户。

代码实现:MyContract 的精妙设计

MyContract 的设计正是为了实现上述策略。

  • play() 函数:

    • 首先,它安排了一个操作来更新 ClimberTimelockdelay 为 0。
    • 紧接着,它安排了另一个操作,授予 MyContract (MY) 自身 PROPOSER_ROLE
    • 最后,它安排了一个空操作,以确保 schedule 的所有操作都能被 execute
    • 通过 _climberTimelock.execute(),这些操作被一次性执行,从而降低了 Timelock 的延时并获得了提案权。
  • play2() 函数:

    • 这一步是核心。它安排了一个升级操作,将 ClimberVault 的实现升级为 YourContract
    • 然后,它执行了这个升级操作。
    • 升级完成后,ClimberVault 的实例就变成了 YourContract 的实例。此时,我们可以调用 YourContractsetSweeper 函数,将玩家 (_player) 设置为新的 sweeper,并将代币 (_token) 发送到 _sweeper 地址。

YourContract 的作用: 这是一个简单的可升级合约,其 setSweeper 函数允许我们控制 sweeper 的地址,并能直接将金库中的代币转移出去。

最终胜利:

通过 MyContractplay()play2() 函数,我们成功地:

  1. 将 Timelock 的延时降至 0。
  2. 获得了 Timelock 的 PROPOSER_ROLE
  3. 通过 Timelock 安排并执行了金库的升级,使其变成了 YourContract
  4. 成功地将玩家地址设置为新的 sweeper
  5. 最后,玩家可以通过 ClimberVaultsweepFunds 函数,将金库中所有的 DVT 代币转移到 recovery 地址。

总结

“Climber”挑战巧妙地融合了 UUPS 升级模式、访问控制和时间锁机制。解题的关键在于理解各个合约的权限和功能,并利用 Timelock 合约本身的 execute 函数来操纵其配置,最终实现对金库的完全控制。这不仅是一次技术实操,更是一场思维的博弈,展现了在复杂的智能合约环境中,如何通过细致的分析和精妙的策略来化解安全危机。

Built with AiAda