漏洞nc 1.15.39.10 10001

132 阅读2分钟

源码:

contract ZT is ERC20("ZERO TOKEN", "ZT"){
    //RULE 1
    bytes32 constant RULE_WITHDRAW_WANT = keccak256(abi.encodePacked("withdraw"));

    //RULE 2
    bytes32 constant RULE_NONE_WANT = keccak256(abi.encodePacked("depositByValue"));


    constructor()public{
        _mint(msg.sender,10000000*10**18);
    }
    function depositByWant(uint _amount)external payable{
        uint amount = _amount.mul(10**18);
        require(msg.value>=amount,"you want to trick me?");
        MkaheChange(msg.sender,amount,RULE_NONE_WANT);
    }

    function withdraw(uint _amount)external payable returns(bool){
        uint amount = _amount.mul(10**18);
        require(balanceOf(msg.sender)>=amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);
        return MkaheChange(msg.sender,amount,RULE_WITHDRAW_WANT);
    }

    function MkaheChange(address to,uint amount,bytes32 ID)internal returns(bool){
        if(ID==RULE_NONE_WANT)
        {
            _balances[msg.sender]=_balances[msg.sender].add(amount);
            return true;
        }else if(ID==RULE_WITHDRAW_WANT){
            bool a;
            (a,)=payable(to).call.value(amount)("");
            require(a,"withdraw fail");
            return true;
        }
        else{
            return false;
        }
    }

    fallback()external payable{
        MkaheChange(
            msg.sender,
            msg.value,
            RULE_NONE_WANT
        );
    }
}
contract ZTstakepool{
    ZT token;
    uint  totalsupply;
    string symbol;
    mapping(address=>uint)internal workbalance;
    mapping(address=>bool)internal passed;

    struct userInfo{
        uint amount;
        uint duration;
        uint startTime;
    }
    mapping(address=>userInfo)internal userDetails;

    constructor()public{
        token =new ZT();
        symbol = "stTGT";
        totalsupply = token.balanceOf(address(this));
    }

    function getDetails(address account)public view returns(userInfo memory){
        return userDetails[account];
    }

    function workBalanceOf(address account)public view returns(uint){
        bool pass=passed[account];
        if(pass){
            return workbalance[account];
        }else{
            return 0;
        }
    }

    function Zt()public view returns(address){
        return address(token);
    }

    function stake(uint amount,uint blocknumber)external{
        require(blocknumber>=1,"At least 1 block");

        userInfo storage user = userDetails[msg.sender];

        user.startTime = block.number;
        user.duration = blocknumber;
        user.amount += amount;

        token.transferFrom(msg.sender,address(this),amount*10**18);
        workbalance[msg.sender] += blocknumber;

    }

    function unstake()external{
        userInfo storage user = userDetails[msg.sender];
        require(block.number>=user.startTime+user.duration,"you are in a hurry ");
        passed[msg.sender] = true;
        uint amount = user.amount;
        user.amount = 0;
        token.transfer(msg.sender,amount*10**18);
    }

    function swap(address from,address to,uint amount)external{
        require(from==address(this)&&to==address(token));
        uint balance = workBalanceOf(msg.sender);
        require(balance>=amount,"exceed");
        workbalance[msg.sender] -= amount;
        token.transfer(msg.sender,amount*10**18);
    }
}

contract setup{
    ZTstakepool public stakePool;
    ZT public erc20;
    bool solve;
    constructor()public{
        stakePool =new ZTstakepool();
        erc20 = ZT(payable(stakePool.Zt()));
    }
    function isSolved()public view returns(bool){
        return solve;
    }
    function complete()public{
        require(erc20.balanceOf(msg.sender)>=500000*10**18);
        solve = true;
    }
}

分析:

ZT是ERC20标准的token,ZTstakepool就像是一个工作池。纵观代码,问题出在stake、unstake、swap三个方法上。只要 passed[msg.sender] = true;就可以执行swap,然而我们可以利用在调用stake前user.startTime+user.duration都为0,满足unstake的require条件,先执行unstake拿到passed,再执行stake输入想要的blocknumber,最后直接swap.

攻击过程

先unstake

image.png

再stake

image.png

查看workbalance是否成功了

image.png

swap提取成ZT

image.png

去ZT合约里查看balance,看转移成功没有

image.png

执行setup 的complete

image.png

成功了!

image.png

flag拿到!! image.png