Appearance
深入虎穴,夺回万千 DVT 币:Climber 挑战的攻防之道
在区块链安全的世界里,每一次的漏洞挖掘都像是一场惊心动魄的寻宝之旅。今天,我们将带你走进 Damn Vulnerable DeFi v4 的“Climber”挑战,揭秘如何巧妙地从一个戒备森严的 DVT 代币金库中,安全地转移全部资产。
挑战背景:层层设防的 DVT 金库
这次的挑战围绕着一个名为 ClimberVault 的智能合约展开,它是一个可升级的合约,遵循 UUPS 模式,并且守护着高达 1000 万枚 DVT 代币。这个金库的“主人”并非普通账户,而是一个 ClimberTimelock 合约。更添一层安全防护的是,这个 Timelock 合约拥有一个“Proposer”角色,负责安排在 1 小时后执行的操作。而金库本身,则额外拥有一个“Sweeper”角色,能够在紧急情况下“扫荡”所有代币。
我们的目标十分明确:解救金库中的所有 DVT 代币,并将它们安全地转移到指定的恢复账户。
攻破防线的关键:理解漏洞与设计
要成功完成挑战,我们需要深入理解各个合约的设计以及它们之间的交互。
ClimberVault (金库合约)
- UUPS 升级模式: 这意味着金库的逻辑可以被替换,而存储的地址(proxy 地址)保持不变。
withdraw函数: 只有合约的owner(即 Timelock)才能调用,并且有提款金额 (WITHDRAWAL_LIMIT) 和时间间隔 (WAITING_PERIOD) 的限制。sweepFunds函数: 只有sweeper角色可以调用,可以一次性转移金库中所有的代币。- 所有权转移: 在
initialize函数中,金库的所有权被转移给了ClimberTimelock合约。
ClimberTimelock (时间锁合约)
PROPOSER_ROLE: 只有拥有此角色的账户才能通过schedule函数安排操作。schedule函数: 安排一个操作,该操作将在delay(默认为 1 小时)后才能被执行。execute函数: 任何人都可以调用,执行已经安排且达到执行时间的schedule操作。updateDelay函数: 只有合约自身(address(this))才能调用,用于更新delay。
ClimberConstants.sol & ClimberErrors.sol: 提供合约的常量和错误定义,是理解合约行为的重要参考。
策略制定:步步为营,化解危机
面对如此严密的防护,我们需要一个多步走的策略:
第一步:打破 Timelock 的时间锁限制。 金库的
withdraw函数受制于 15 天的等待期,而且提款额度有限。而ClimberTimelock的delay只有 1 小时。如果我们能够将 Timelock 的delay设置为 0,那么schedule和execute的时间差就会消失,操作可以近乎实时地执行。第二步:夺取 Timelock 的
PROPOSER_ROLE。 金库的sweepFunds功能是直接转移所有代币的终极手段,但只有sweeper才能调用。而ClimberTimelock的owner是 Timelock 本身,可以通过 Timelock 的execute函数来执行。为了能让我们的合约(或玩家账户)来触发sweepFunds,我们需要获得 Timelock 的PROPOSER_ROLE,这样我们才能通过 Timelock 来升级金库合约,并为sweepFunds设置我们控制的sweeper。第三步:升级金库,指定“我们”为 Sweeper。 Timelock 合约是金库的 owner,并且 Timelock 的 owner 是其自身 (
address(this))。 Timelock 的ADMIN_ROLE由deployer和 Timelock 自己持有。ADMIN_ROLE可以授予PROPOSER_ROLE。 通过ClimberTimelock的execute函数,我们可以:- 将 Timelock 的
delay设置为 0。 - 将
PROPOSER_ROLE授予我们的合约(MY)。 - 然后,我们可以利用 Timelock 的
PROPOSER_ROLE来安排一个升级金库的操作,将金库的实现替换成一个我们自己编写的,能够设置sweeper的合约 (YourContract)。
- 将 Timelock 的
第四步:执行 Sweeper 角色,转移代币。 一旦金库被升级为
YourContract,并且YourContract的setSweeper函数被调用,将我们的玩家地址设置为sweeper,那么我们就可以直接调用ClimberVault的sweepFunds函数,将所有 DVT 代币转移到预设的恢复账户。
代码实现:MyContract 的精妙设计
MyContract 的设计正是为了实现上述策略。
play()函数:- 首先,它安排了一个操作来更新
ClimberTimelock的delay为 0。 - 紧接着,它安排了另一个操作,授予
MyContract(MY) 自身PROPOSER_ROLE。 - 最后,它安排了一个空操作,以确保
schedule的所有操作都能被execute。 - 通过
_climberTimelock.execute(),这些操作被一次性执行,从而降低了 Timelock 的延时并获得了提案权。
- 首先,它安排了一个操作来更新
play2()函数:- 这一步是核心。它安排了一个升级操作,将
ClimberVault的实现升级为YourContract。 - 然后,它执行了这个升级操作。
- 升级完成后,
ClimberVault的实例就变成了YourContract的实例。此时,我们可以调用YourContract的setSweeper函数,将玩家 (_player) 设置为新的sweeper,并将代币 (_token) 发送到_sweeper地址。
- 这一步是核心。它安排了一个升级操作,将
YourContract 的作用: 这是一个简单的可升级合约,其 setSweeper 函数允许我们控制 sweeper 的地址,并能直接将金库中的代币转移出去。
最终胜利:
通过 MyContract 的 play() 和 play2() 函数,我们成功地:
- 将 Timelock 的延时降至 0。
- 获得了 Timelock 的
PROPOSER_ROLE。 - 通过 Timelock 安排并执行了金库的升级,使其变成了
YourContract。 - 成功地将玩家地址设置为新的
sweeper。 - 最后,玩家可以通过
ClimberVault的sweepFunds函数,将金库中所有的 DVT 代币转移到recovery地址。
总结
“Climber”挑战巧妙地融合了 UUPS 升级模式、访问控制和时间锁机制。解题的关键在于理解各个合约的权限和功能,并利用 Timelock 合约本身的 execute 函数来操纵其配置,最终实现对金库的完全控制。这不仅是一次技术实操,更是一场思维的博弈,展现了在复杂的智能合约环境中,如何通过细致的分析和精妙的策略来化解安全危机。