DatingDapp

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

[M-1] Reentrancy: Reentrancy Risk in SoulboundProfileNFT::mintProfile Function

Summary

The mintProfile function in the SoulboundProfileNFT contract is vulnerable to reentrancy attacks due to the incorrect order of operations. The function makes an external call to _safeMint before updating state variables, which allows a malicious contract to re-enter and potentially manipulate the contract's state before the minting process is finalized.

Vulnerability Details

The vulnerability arises because _safeMint is an external call that interacts with msg.sender, which could be a malicious smart contract. If msg.sender is a contract with a fallback function, it can re-enter mintProfile or other functions before profileToToken[msg.sender] is updated.

Vulnerability code:

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); // ❌ External call before state update
_profiles[tokenId] = Profile(name, age, profileImage);
profileToToken[msg.sender] = tokenId;
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}

Exploit Scenario:

  1. Attacker deploys a malicious contract that has a fallback function capable of calling mintProfile again before state changes take effect.

  2. The attacker calls mintProfile(), and _safeMint(msg.sender, tokenId) triggers the attacker’s fallback function.

  3. The fallback function re-enters mintProfile, minting multiple NFTs before profileToToken[msg.sender] is set.

  4. The attacker obtains multiple profiles despite the require(profileToToken[msg.sender] == 0) check.

PoC:

contract MaliciousContract {
SoulboundProfileNFT public target;
constructor(address _target) {
target = SoulboundProfileNFT(_target);
}
function attack() external {
target.mintProfile("Attacker", 25, "evil_image");
}
fallback() external {
if (address(target).balance > 0) {
target.mintProfile("Attacker", 25, "evil_image");
}
}
}

Impact

Medium Severity: A malicious user can bypass the one-profile-per-user restriction and mint multiple profiles.

Data Corruption: The _profiles mapping can be corrupted, linking multiple token IDs to a single msg.sender.

Protocol Integrity Compromise: The immutability and uniqueness of soulbound profiles are undermined.

Tools Used

Slither (for automated detection)
Manual Review (to verify exploitability)

Recommendations

To prevent reentrancy, the contract should follow the Checks-Effects-Interactions pattern, ensuring that state changes occur before external calls.

function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
uint256 tokenId = ++_nextTokenId;
+ profileToToken[msg.sender] = tokenId; // ✅ Update state FIRST
+ _profiles[tokenId] = Profile(name, age, profileImage); // ✅ Store metadata BEFORE external call
+ _safeMint(msg.sender, tokenId); // ✅ External call LAST
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}

Additional Considerations

  1. Use Reentrancy Guards (nonReentrant) from OpenZeppelin's ReentrancyGuard.

  2. Emit an event before making external calls to improve transparency.

  3. Perform additional validation on msg.sender if required.

  4. By implementing these fixes, the mintProfile function will be protected against reentrancy attacks, ensuring the integrity of the soulbound profile system.

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.