本文分析了Damn Vulnerable DeFi的Unstoppable挑战,该挑战基于ERC4626 Token Vault,目标是利用漏洞阻止vault提供flash loan。通过直接向vault合约转账少量token,破坏了totalAssets和totalSupply之间的平衡,从而使flashLoan功能失效,合约停止提供服务。
Damn Vulnerable DEFI: Unstoppable
这个挑战基于 ERC4626 Token Vault,其中 vault 以闪电贷(flashloan)的形式借给借款人,而闪电贷接收者必须支付一小笔费用。之前,我们已经解决了所有 ethernaut 挑战并撰写了一些解决方案文章。你可以在我的 medium 博客上找到。
有一个代币化的 vault,其中存入了 100 万个 DVT 代币。在宽限期结束之前,它提供免费的闪电贷。
为了在 100% 无许可之前发现任何错误,开发人员决定在测试网上运行一个 live beta。有一个监控合约来检查闪电贷功能的liveness。
从 10 个 DVT 代币的余额开始,证明有可能停止 vault。它必须停止提供闪电贷。
这个挑战中有两个合约,UnstoppableVault.sol 和 UnstoppableMonitor.sol。我们将主要关注 Unstoppable Vault 合约,因为漏洞就在这里。
contract UnstoppableVault is IERC3156FlashLender, ReentrancyGuard, Owned, ERC4626, Pausable {
......
function flashLoan(IERC3156FlashBorrower receiver, address _token, uint256 amount, bytes calldata data)
external
returns (bool)
{
if (amount == 0) revert InvalidAmount(0); // 提前失败
if (address(asset) != _token) revert UnsupportedCurrency(); // 强制执行 ERC3156 要求
uint256 balanceBefore = totalAssets();
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // 强制执行 ERC4626 要求
// 转出代币 + 在接收者上执行回调
ERC20(_token).safeTransfer(address(receiver), amount);
// 回调必须返回 magic 值,否则假定它失败了
uint256 fee = flashFee(_token, amount);
if (
receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data)
!= keccak256("IERC3156FlashBorrower.onFlashLoan")
) {
revert CallbackFailed();
}
// 从接收者处提取 amount + fee,然后将 fee 支付给接收者
ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee);
ERC20(_token).safeTransfer(feeRecipient, fee);
return true;
}
vault 合约中有一个 flashLoan 函数,每个人都可以为自己申请闪电贷。此函数有一些需要满足的必要条件:
uint256 balanceBefore = totalAssets();
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // 强制执行 ERC4626
convertToShares 函数返回应该为接收者铸造多少 share 的 uint256 值。代币池和 share 池在 vault 合约中始终相等,它们永远不会被错误计算。
漏洞在于检查 totalAssests 和 totalSupply 是否相等的条件,如果不相等,则 revert 交易。记住,我们必须停止 vault 合约,这样任何人都无法claim闪电贷。
我们知道玩家有 10 个 DVT 来开始这个挑战,如果我们不使用任何函数而将少量代币直接转移到 vault 合约会发生什么?明白了吗?是的,这将破坏 vault 合约的核算系统,并在检查 totalAssets 和 totalSupply 的条件之间产生冲突。
function test_unstoppable() public checkSolvedByPlayer {
///利用:直接代币转账破坏了 vault 中的 share 计算
token.transfer(address(vault), INITIAL_PLAYER_TOKEN_BALANCE);
}
在 unstoppable 测试合约中运行此代码并解决挑战
这是我针对此挑战的完整解决方案代码,你可以 fork 我的 repo damn-vulnerbale-challenge-solutions-v4,并在本地使用 forge test 进行测试。
- 原文链接: blog.blockmagnates.com/d...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!