80 lines
3.3 KiB
Markdown
80 lines
3.3 KiB
Markdown
# M-01. `SoulboundProfileNFT::blockProfile` make it possible to recreate the profile
|
|
|
|
## This is the final report, not submitted by me.
|
|
|
|
## 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);
|
|
}
|
|
``` |