Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: low
Invalid

Informational: Allowing the ZERO_ADDRESS to participate in the Raffle makes Snek Raffle vulnerable to Denial of Service (DoS) attacks.

Summary

Informational: The enter_raffle external function allows participation from the ZERO_ADDRESS in the Snek Raffle, despite the critical fact that the ZERO_ADDRESS is incapable of holding an NFT.

Vulnerability Details

Please review the enter_raffle external function starting from Line 113. There's currently no assert/revert check in place for the ZERO_ADDRESS.

# External Functions
@external
@payable
def enter_raffle():
"""Enter the raffle by sending the entrance fee."""
------> # here should have a zero_address restriction assertion/revert conditional
assert msg.value == ENTRANCE_FEE, ERROR_SEND_MORE_TO_ENTER_RAFFLE
assert self.raffle_state == RaffleState.OPEN, ERROR_RAFFLE_NOT_OPEN
self.players.append(msg.sender)
log RaffleEntered(msg.sender)

Due to the absence of a ZERO_ADDRESS check in the enter_raffle function, the Snek Raffle lacks a contingency plan to prevent the minting of an NFT and payment of the reward amount to a ZERO_ADDRESS. This could lead to failure whenever the VRF - fulfillRandomWords function attempts to mint an NFT for the ZERO_ADDRESS.

Additionally, there are no checks or sanitization for the ZERO_ADDRESS even within the VRF - fulfillRandomWords internal function.

@internal
def fulfillRandomWords(request_id: uint256, random_words: uint256[MAX_ARRAY_SIZE]):
index_of_winner: uint256 = random_words[0] % len(self.players)
recent_winner: address = self.players[index_of_winner]
self.recent_winner = recent_winner
self.players = []
self.raffle_state = RaffleState.OPEN
self.last_timestamp = block.timestamp
rarity: uint256 = random_words[0] % 3
self.tokenIdToRarity[ERC721._total_supply()] = rarity
log WinnerPicked(recent_winner)
ERC721._mint(recent_winner, ERC721._total_supply())
-----------------^
send(recent_winner, self.balance)

Impact

If a Raffle spinning includes a ZERO_ADDRESS as a participant and that ZERO_ADDRESS becomes the winner, the Raffle will become indefinitely stuck and won't allow any further sessions to spin the Raffle. This vulnerability could potentially be exploited as a Denial of Service (DoS) attack if a malicious user discovers it within the Raffle. An attacker could exploit (Not Possible on Live chains) this vulnerability by deploying a malicious contract that pushes a ZERO_ADDRESS as a participant into the Raffle. However, the DoS attack can only occur if the ZERO_ADDRESS is declared the winner which is not possible into the real world or live chains. Why? Because EVM automatically selects the msg.sender=deployer or contract and that can't be a zero address.

DoS with ZERO_ADDRESS
  • Place the following test at the bottom of the tests/snek_raffle_test.py file.

def test_nft_mint_fails_on_zero_address(raffle_boa, vrf_coordinator_boa, entrance_fee):
boa.env.set_balance(ZERO_ADDRESS, STARTING_BALANCE)
with boa.env.prank(ZERO_ADDRESS):
raffle_boa.enter_raffle(value=entrance_fee)
boa.env.time_travel(seconds=INTERVAL + 1)
with boa.reverts("ERC721: mint to the zero address"):
vrf_coordinator_boa.fulfillRandomWords(0, raffle_boa.address)
  • Save the file, open the terminal, and execute the following command. Don't forget to activate the virtual environment for Vyper support.

pytest -v tests/snek_raffle_test.py::test_nft_mint_fails_on_zero_address -s
  • See, the logs...

==================================================================== test session starts =====================================================================
platform linux -- Python 3.10.12, pytest-8.0.2, pluggy-1.4.0 -- /home/theirrationalone/vyperenv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/home/theirrationalone/first-flights/2024-03-snek-raffle/.hypothesis/examples'))
rootdir: /home/theirrationalone/first-flights/2024-03-snek-raffle
plugins: titanoboa-0.1.8, cov-4.1.0, hypothesis-6.98.17
collected 1 item
tests/snek_raffle_test.py::test_nft_mint_fails_on_zero_address PASSED
===================================================================== 1 passed in 1.56s ======================================================================

As evidenced by the successful passing of the test, it is apparent that the Raffle is vulnerable to a Denial of Service (DoS) Attack (theoretically Possible only) under certain circumstances.

Tools Used

Manual Review, Pytest

Recommendations

  • Add a conditional assert check to restrict the ZERO_ADDRESS from entering the raffle.

Please update the contracts/snek_raffle.vy file as provided below...

.
.
.
# Errors
ERROR_NOT_ENDED: constant(String[25]) = "SnekRaffle: Not ended"
ERROR_TRANSFER_FAILED: constant(String[100]) = "SnekRaffle: Transfer failed"
ERROR_SEND_MORE_TO_ENTER_RAFFLE: constant(String[100]) = "SnekRaffle: Send more to enter raffle"
ERROR_RAFFLE_NOT_OPEN: constant(String[100]) = "SnekRaffle: Raffle not open"
ERROR_NOT_COORDINATOR: constant(String[46]) = "SnekRaffle: OnlyCoordinatorCanFulfill"
+ ERROR_ZERO_ADDRESS_NOT_ALLOWED: constant(String[50]) = "SnekRaffle: Zero Address not allowed"
.
.
.
# External Functions
@external
@payable
def enter_raffle():
"""Enter the raffle by sending the entrance fee."""
+ assert msg.sender != empty(address), ERROR_ZERO_ADDRESS_NOT_ALLOWED
assert msg.value == ENTRANCE_FEE, ERROR_SEND_MORE_TO_ENTER_RAFFLE
assert self.raffle_state == RaffleState.OPEN, ERROR_RAFFLE_NOT_OPEN
self.players.append(msg.sender)
log RaffleEntered(msg.sender)
.
.
.

Save the file.

Mitigation PoC:
  • Place the following test at the bottom of the tests/snek_raffle_test.py file.

def test_reverts_on_zero_address_entry(raffle_boa, entrance_fee):
boa.env.set_balance(ZERO_ADDRESS, STARTING_BALANCE)
with boa.env.prank(ZERO_ADDRESS):
with boa.reverts("SnekRaffle: Zero Address not allowed"):
raffle_boa.enter_raffle(value=entrance_fee)
  • Save the file, open the terminal, and execute the following command. Don't forget to activate the virtual environment for Vyper support.

pytest -v tests/snek_raffle_test.py::test_reverts_on_zero_address_entry -s
  • See, the logs...

==================================================================== test session starts =====================================================================
platform linux -- Python 3.10.12, pytest-8.0.2, pluggy-1.4.0 -- /home/theirrationalone/vyperenv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/home/theirrationalone/first-flights/2024-03-snek-raffle/.hypothesis/examples'))
rootdir: /home/theirrationalone/first-flights/2024-03-snek-raffle
plugins: titanoboa-0.1.8, cov-4.1.0, hypothesis-6.98.17
collected 1 item
tests/snek_raffle_test.py::test_reverts_on_zero_address_entry PASSED
===================================================================== 1 passed in 1.55s ======================================================================
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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