Summary
The SoulboundProfileNFT::mintProfile
function is vulnerable to front-running attacks where an attacker can observe pending profile creation transactions and front-run them to claim desirable profile names or prevent specific users from registering their profiles.
Vulnerability Details
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);
_profiles[tokenId] = Profile(name, age, profileImage);
profileToToken[msg.sender] = tokenId;
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}
The function doesn't have any protection against transaction ordering manipulation, allowing attackers to front-run profile creation.
Proof Of Concept
function testProfileFrontRunning() public {
string memory desiredName = "VitalikButerin";
string memory desiredImage = "ipfs://vitalik";
vm.startPrank(alice);
vm.deal(alice, 1 ether);
vm.stopPrank();
vm.startPrank(attacker);
nft.mintProfile(desiredName, 25, desiredImage);
uint256 attackerTokenId = nft.profileToToken(attacker);
assertGt(attackerTokenId, 0, "Attacker should have a token");
vm.stopPrank();
vm.startPrank(alice);
nft.mintProfile(desiredName, 30, desiredImage);
uint256 aliceTokenId = nft.profileToToken(alice);
assertEq(aliceTokenId, 0, "Alice should not have a token");
vm.stopPrank();
}
Impact
High severity because:
Attackers can steal valuable or recognizable profile names
Users can be prevented from creating their legitimate profiles
Can be used for impersonation and social engineering
Damages platform reputation and user trust
Opens up possibilities for ransom/extortion of profile names
Tools Used
Recommendations
Implement a commit-reveal scheme for profile creation:
mapping(address => bytes32) public nameCommitments;
mapping(address => uint256) public commitmentTimestamps;
function commitProfileName(bytes32 commitment) external {
nameCommitments[msg.sender] = commitment;
commitmentTimestamps[msg.sender] = block.timestamp;
}
function revealAndMintProfile(string memory name, uint8 age, string memory profileImage, bytes32 salt) external {
require(block.timestamp >= commitmentTimestamps[msg.sender] + 1 hours, "Too early");
require(nameCommitments[msg.sender] == keccak256(abi.encodePacked(name, salt)), "Invalid commitment");
uint256 tokenId = ++_nextTokenId;
_safeMint(msg.sender, tokenId);
_profiles[tokenId] = Profile(name, age, profileImage);
profileToToken[msg.sender] = tokenId;
delete nameCommitments[msg.sender];
delete commitmentTimestamps[msg.sender];
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}