DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: medium
Valid

App owner can have users' funds locked by blocking them

Summary

The SoulboundProfileNFT::blockProfile function uses delete profileToToken[blockAddress], which resets profileToToken[blockAddress] to 0. Since the mintProfile function checks for an existing profile by verifying that profileToToken[msg.sender] == 0, a blocked account can be recreated by simply minting a new profile. This behavior bypasses the intended permanent block functionality.

Vulnerability Details

By deleting the mapping entry for a blocked account, the contract inadvertently allows a new mintProfile call to pass the check require(profileToToken[msg.sender] == 0, "Profile already exists"). Essentially, once an account is blocked, its associated mapping entry is cleared, so the condition to identify an account with an existing profile is no longer met. This loophole enables a blocked account to recreate its profile, undermining the purpose of blocking.

Impact

A blocked account, which should be permanently barred from engaging with the platform, can circumvent this restriction by re-minting its profile.
The integrity of the platform is compromised, as blocked users could regain access and potentially perform further malicious actions.

POC

function testRecereationOfBlockedAccount() public {
// Alice mints a profile successfully
vm.prank(user);
soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice");
// Owner blocks Alice's account, which deletes Alice profile mapping
vm.prank(owner);
soulboundNFT.blockProfile(user);
// The blocked user (Alice) attempts to mint a new profile.
// Due to the reset mapping value (0), the require check is bypassed.
vm.prank(user);
soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice");
}

Tools Used

  • Foundry: Utilized for testing the contract, including validating the minting and blocking behavior.

  • Manual Code Review: An analysis of the Solidity code confirmed that the delete operation resets the mapping value, creating the vulnerability.

Recommendations

  • When blocking an account, implement a mechanism to permanently mark that address as blocked rather than simply deleting an entry. For example, maintain a separate mapping (e.g., isBlocked) to record blocked accounts, and update mintProfile to check if an account is permanently barred from minting: Example modification:

+ mapping(address => bool) public isBlocked;
...
function mintProfile(string memory name, uint8 age, string memory profileImage) external {
+ require(!isBlocked[msg.sender], "Account is permanently blocked");
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);
}
...
function blockProfile(address blockAddress) external onlyOwner {
uint256 tokenId = profileToToken[blockAddress];
require(tokenId != 0, "No profile found");
_burn(tokenId);
delete profileToToken[blockAddress];
delete _profiles[tokenId];
+ isBlocked[blockAddress] = true;
emit ProfileBurned(blockAddress, tokenId);
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 7 days ago
Submission Judgement Published
Validated
Assigned finding tags:

[M-03] App owner can have users' funds locked by blocking them

## Description App owner can block users at will, causing users to have their funds locked. ## Vulnerability Details `SoulboundProfileNFT::blockProfile` can block any app's user at will. ```js /// @notice App owner can block users 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); } ``` ## Proof of Concept The following code demonstrates the scenario where the app owner blocks `bob` and he is no longer able to call `LikeRegistry::likeUser`. Since the contract gives no posibility of fund withdrawal, `bob`'s funds are now locked. Place `test_blockProfileAbuseCanCauseFundLoss` in `testSoulboundProfileNFT.t.sol`: ```js function test_blockProfileAbuseCanCauseFundLoss() public { vm.deal(bob, 10 ether); vm.deal(alice, 10 ether); // mint a profile NFT for bob vm.prank(bob); soulboundNFT.mintProfile("Bob", 25, "ipfs://profileImage"); // mint a profile NFT for Alice vm.prank(alice); soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage"); // alice <3 bob vm.prank(alice); likeRegistry.likeUser{value: 1 ether}(bob); vm.startPrank(owner); soulboundNFT.blockProfile(bob); assertEq(soulboundNFT.profileToToken(msg.sender), 0); vm.startPrank(bob); vm.expectRevert("Must have a profile NFT"); // bob is no longer able to like a user, as his profile NFT is deleted // his funds are effectively locked likeRegistry.likeUser{value: 1 ether}(alice); } ``` And run the test: ```bash $ forge test --mt test_blockProfileAbuseCanCauseFundLoss Ran 1 test for test/testSoulboundProfileNFT.t.sol:SoulboundProfileNFTTest [PASS] test_blockProfileAbuseCanCauseFundLoss() (gas: 326392) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.42ms (219.63µs CPU time) Ran 1 test suite in 140.90ms (1.42ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests) ``` ## Impact App users can have their funds locked, as well as miss out on potential dates. ## Recommendations Add a voting mechanism to prevent abuse and/or centralization of the feature.

Support

FAQs

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

Give us feedback!