Appearance
揭秘 Puppet:一场 DVT 的“空手套白狼”的 DeFi 冒险
在去中心化金融(DeFi)的世界里,聪明的套利和漏洞挖掘是永恒的主题。今天,我们将带你走进 Damn Vulnerable DeFi 的“Puppet”挑战,一步步揭示如何利用一个看似安全的借贷池,上演一场精彩的“空手套白狼”,最终将所有珍贵的 Damn Valuable Tokens (DVTs) 收入囊中。
挑战背景:一个“诱人”的借贷池
想象一下,有一个借贷池,你可以从中借走 Damn Valuable Tokens (DVTs)。但有个小小的“规矩”:你需要用两倍于借款金额的 ETH 作为抵押。这个池子目前拥有 100,000 个 DVTs 的充裕流动性。
更“巧合”的是,在古老的 Uniswap v1 交易所上,恰好有一个 DVT 市场,拥有 10 ETH 和 10 DVT 的流动性。
你的任务是:**拯救借贷池中所有的 DVTs,并将它们安全地存入指定的回收账户。**而你,作为挑战者,拥有 25 ETH 和 1000 DVTs 的初始资金。
核心漏洞:价格操纵与套利
仔细审视,你会发现两个关键点:
- 借贷池的“价格预言机”:
PuppetPool合约通过 Uniswap v1 的流动性来计算 DVT 的价格。它依赖于uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair)来获取 DVT 的 ETH 价格。 - Uniswap v1 的兑换逻辑: Uniswap v1 的兑换是基于恒定乘积做市商模型,即
x * y = k。这意味着,通过向池子中注入或取出代币,可以影响其价格。
关键洞察: 如果我们能在 Uniswap v1 上操纵 DVT 的价格,那么 PuppetPool 合约计算出的 DVT 价格也会随之改变。而 PuppetPool 的借款逻辑是根据这个“伪造”的价格来计算所需抵押物的。
攻击路径:一步步瓦解防御
我们的目标是清空 PuppetPool 中的 DVT。以下是实现这一目标的策略:
获取 DVT 的授权: 首先,我们需要让我们的攻击合约能够代表我们从
PuppetPool中借出 DVT。而PuppetPool的borrow函数实际上是通过token.transfer(recipient, amount)来实现的。这意味着,如果攻击合约作为recipient,它就可以直接获得 DVT。引入“中间人”:
PuppetPool要求用 ETH 作为抵押物来借 DVT。但我们手上只有 DVT,而不是 ETH。这里就需要利用 Uniswap v1 了。我们可以通过token.approve()和exchange.tokenToEthSwapInput()来将我们手上的 DVT 兑换成 ETH。巧妙的“套利”:
第一步:操纵价格。 我们用自己的一部分 DVT,在 Uniswap v1 中进行一个“不正当”的交易。通过
token.transferFrom(player, address(this), dvtValue),将一部分 DVT 转到我们攻击合约的地址,然后利用token.approve(address(exchange), ...)和exchange.tokenToEthSwapInput(dvtValue, 1, deadline),将大量的 DVT 换成 ETH。- 重点: 这个操作会显著增加 Uniswap v1 中 DVT 的数量,并减少 ETH 的数量。根据
x * y = k,这会极大地拉低 DVT 的 ETH 价格。
- 重点: 这个操作会显著增加 Uniswap v1 中 DVT 的数量,并减少 ETH 的数量。根据
第二步:利用“低价”借贷。 现在,Uniswap v1 上的 DVT 价格已经非常低了。
PuppetPool会根据这个被操纵的价格来计算借款所需的 ETH 抵押物。- 我们计算出
PuppetPool中剩余的 DVT 总量(token.balanceOf(address(pool)))。 - 然后,调用
pool.calculateDepositRequired(amountToBorrow)来获取这个“低价”下的所需抵押物。 - 关键在于,此时我们手里有很多从 Uniswap v1 换来的 ETH,足以支付这个极低的抵押物金额。
- 最后,调用
pool.borrow{value: deposit}(amountToBorrow, recovery),用这笔“微不足道”的 ETH 抵押物,从PuppetPool中借走所有的 DVT,并将它们直接发送到指定的recovery地址。
- 我们计算出
完成任务: 所有的 DVT 都被转移到了
recovery地址,PuppetPool中的 DVT 被清空。挑战成功!
关键代码解读
在提供的 MyContract 解决方案中,我们可以看到攻击逻辑的精髓:
token.permit()和token.transferFrom(): 允许攻击合约代为操作玩家的 DVT。token.approve()和exchange.tokenToEthSwapInput(): 将 DVT 兑换成 ETH,并在此过程中操纵 Uniswap v1 的价格。pool.calculateDepositRequired()和pool.borrow{value: deposit}(): 利用被操纵的低价格,用极少的 ETH 借走PuppetPool中所有的 DVT,并直接发送到recovery地址。
总结
“Puppet”挑战巧妙地利用了 DeFi 中两个核心组件——借贷池和去中心化交易所——之间的联动。通过对 Uniswap v1 价格的操纵,使得借贷池的抵押物计算出现偏差,从而实现了“空手套白狼”的壮举。这个挑战提醒我们,在 DeFi 世界中,任何依赖外部价格源的协议,都可能面临价格操纵的风险,必须谨慎设计和审计。
这场 DVT 的“Puppet”冒险,是一次对 DeFi 安全性、套利机制和合约漏洞的深刻体验。你准备好迎接下一个挑战了吗?