DatingDapp

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

Blocked User Can Call `SoulboundProfileNFT::mintProfile` Again

Summary

Due to missing blocked profile handling, any blocked profile can call SoulboundProfileNFT::mintProfile again to mint and take part on the system. The purpose of blocking a profile no longer valid, since they could mint a new profile and continue interacting with existing LikeRegistry and MultiSig.

Vulnerability Details

When SoulboundProfileNFT::blockProfile is called by the owner, it will burn the profile token, delete onchain variable related to the burnt tokenId and delete the profileToToken of the address.

function blockProfile(address blockAddress) external onlyOwner {
uint256 tokenId = profileToToken[blockAddress];
require(tokenId != 0, "No profile found");
_burn(tokenId);
delete profileToToken[blockAddress];
delete _profiles[tokenId];
emit ProfileBurned(blockAddress, tokenId);
}

So after the SoulboundNFT::blockProfile being called, no one can call LikeRegistry::likeUser to the blocked address.

function likeUser(address liked) external payable {
[...]
require(profileNFT.profileToToken(msg.sender) != 0, "Must have a profile NFT");
require(profileNFT.profileToToken(liked) != 0, "Liked user must have a profile NFT");
[...]
}

The LikeRegistry::likeUser require both the caller to have existing profile tokens. So blocked user unable to like or being liked.

But the SoulboundProfileNFT::mintProfile doesn't have any check if the user or profile is blocked or not. So if someone has already blocked, they can call the mint function again.

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);
}

There is no check if sender has its profile blocked or not previously.

POC

function testBlockedProfileMintAgain() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 tokenId = soulboundNFT.profileToToken(user);
assertEq(tokenId, 1, "Token should exist before blocking");
vm.prank(owner);
soulboundNFT.blockProfile(user);
uint256 newTokenId = soulboundNFT.profileToToken(user);
assertEq(newTokenId, 0, "Token should be removed after blocking");
vm.prank(user);
vm.expectRevert();
// blocked profile should not be able to mint from the same wallet address again
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
}

Impact

  • Any blocked profile can mint again and continue their activity at this protocol

  • Blocked profile can mint again and mint a profile from another wallet, then make the other profile to match with his blocked account, and withdraw all of their own fund.

Recommendations

Add new storage variable to check if a wallet address is blocked or not.

--- a/src/SoulboundProfileNFT.sol
+++ b/src/SoulboundProfileNFT.sol
@@ -20,6 +20,7 @@ contract SoulboundProfileNFT is ERC721, Ownable {
mapping(address => uint256) public profileToToken; // Maps user to their profile NFT
mapping(uint256 => Profile) private _profiles; // Stores profile metadata
+ mapping(address => bool) public profileBlocked;
event ProfileMinted(address indexed user, uint256 tokenId, string name, uint8 age, string profileImage);
event ProfileBurned(address indexed user, uint256 tokenId);
@@ -29,6 +30,7 @@ contract SoulboundProfileNFT is ERC721, Ownable {
/// @notice Mint a soulbound NFT representing the user's profile.
function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
+ require(profileBlocked[msg.sender] == false, "Profile is blocked");
uint256 tokenId = ++_nextTokenId;
_safeMint(msg.sender, tokenId);
@@ -61,6 +63,7 @@ contract SoulboundProfileNFT is ERC721, Ownable {
_burn(tokenId);
delete profileToToken[blockAddress];
delete _profiles[tokenId];
+ profileBlocked[blockAddress] = true;
emit ProfileBurned(blockAddress, tokenId);
}
Updates

Appeal created

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

finding_blocked_user_can_recreate_a_profil

Likelihood: Low, any blocked users. Impact: High, not really blocked.

Support

FAQs

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