Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Random values in ChoosingRam::selectRamIfNotSelected and ChoosingRam::increaseValuesOfParticipants are only pseudo random.

[H-3] Random values in ChoosingRam::selectRamIfNotSelected and ChoosingRam::increaseValuesOfParticipants are only pseudo random. It allows users to influence and predict outcome of which ramNFT will be selected and hence enable gaming of the outcome of the Dussehra protocol.

Description:
Hashing block.timestamp and block.prevrandao together at ChoosingRam::selectRamIfNotSelected creates a predictable final number. It is not a truly random number. It is possible for an organiser to calculate the outcome before calling the function, allowing them to choose who will be the winner.

Similarly, hashing block.timestamp, block.prevrandao and msg.sender together at ChoosingRam::increaseValuesOfParticipants also creates a predictable final number. This time, though, the addition of msg.sender also allows the final number to be influenced, choosing which of the two participants will receive the increased value.

Impact:

  1. The organiser can choose who get to be selected as Ram.

  2. Any participant can game the seemingly random selection of tokenIdOfChallenger or tokenIdOfAnyPerticipent at the increaseValuesOfParticipants.

A central element of the intended functionality of the protocol is the random selection of Ram. This vulnerability breaks this intended functionality.

Proof of Concept:

  1. The organiser knows ahead of time the block.timestamp andblock.prevrandao and uses this calculate outcome of calculation of "random" value.

  2. When this value brings up the correct RamNFT id, organiser calls the selectRamIfNotSelected function.

  3. The expected participant is selected as the winner.

Proof of Concept Place the following in `Dussehra.t.sol`.
function test_organiserCanChooseWinner() public participants {
uint256 tokenThatShouldWin = 0;
// check that player1 is owner of ramNFT token no. 0.
assertEq(ramNFT.getCharacteristics(tokenThatShouldWin).ram, player1);
uint256 thisIsSoNotRandom = 99999; // should not initialise to 0 as this equals `tokenThatShouldWin`.
uint256 j = 1;
while (thisIsSoNotRandom != tokenThatShouldWin) {
vm.warp(1728691200 + j);
thisIsSoNotRandom = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % 2;
j++;
}
// when we reached the correct value, we run the selectRamIfNotSelected function.
vm.startPrank(organiser);
choosingRam.selectRamIfNotSelected();
vm.stopPrank();
// player1, owner of ramNFT no 0 is selected as Ram.
vm.assertEq(choosingRam.selectedRam(), player1);
}

Recommended Mitigation: Use an off-chain verified random number generator. The most popular one is Chainlink VRF, but others exist. As this will require extensive refactoring of code, I did not write out the mitigation here.

Updates

Lead Judging Commences

bube Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Weak randomness in `ChoosingRam::selectRamIfNotSelected`

The organizer is trusted, but the function `ChoosingRam::selectRamIfNotSelected` uses a way to generate a random number that is not completely random.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.