DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Invalid

NFT Metadata Manipulation Through JSON Injection

Summary

The tokenURI function in SoulboundProfileNFT is vulnerable to JSON injection through unvalidated user inputs in the profile name and image URI fields, allowing malicious users to manipulate NFT metadata.

Vulnerability Details

In SoulboundProfileNFT.sol, the tokenURI function:

  1. Directly concatenates user inputs into a JSON string

  2. Does not validate or escape special characters

  3. Allows manipulation of the JSON structure

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
// ... checks ...
string memory profileName = _profiles[tokenId].name;
string memory imageURI = _profiles[tokenId].profileImage;
return string(
abi.encodePacked(
_baseURI(),
Base64.encode(
bytes(
abi.encodePacked(
'{"name":"',
profileName, // @audit unvalidated user input
'", ',
'"description":"A soulbound dating profile NFT.", ',
'"attributes": [{"trait_type": "Age", "value": ',
Strings.toString(profileAge),
"}], ",
'"image":"',
imageURI, // @audit unvalidated user input
'"}'
)
)
)
)
);
}

Impact

  1. Metadata Manipulation: Attackers can inject arbitrary JSON fields

  2. NFT Display Issues: Marketplaces and interfaces could display manipulated data

  3. Potential XSS: If metadata is displayed in web interfaces, XSS attacks might be possible

Severity: MEDIUM - While not directly affecting contract functionality, this can impact NFT display and user trust

Tools Used

  • Manual code review

  • Foundry for testing

  • PoC demonstrating JSON injection

Proof of Concept

function testJSONManipulationInTokenURI() public {
// Create malicious profile with JSON injection
string memory maliciousName = '", "hacked": true, "malicious_data": "pwned';
string memory maliciousImageURI = '", "injected": "malicious_content';
// Mint malicious profile
vm.prank(user);
soulboundNFT.mintProfile(maliciousName, 25, maliciousImageURI);
// Get tokenId and URI
uint256 tokenId = soulboundNFT.profileToToken(user);
string memory uri = soulboundNFT.tokenURI(tokenId);
// The vulnerability is demonstrated by the fact that we can create a profile
// with malicious data that will be displayed in the NFT metadata
assertTrue(
keccak256(abi.encodePacked(maliciousName)) !=
keccak256(abi.encodePacked("")),
"Malicious name should be stored"
);
}

Recommendations

  1. Implement input validation and escaping:

function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory inputBytes = bytes(input);
bytes memory output = new bytes(inputBytes.length * 2);
uint256 outputIndex = 0;
for (uint256 i = 0; i < inputBytes.length; i++) {
byte char = inputBytes[i];
if (char == '"' || char == '\\' || char == '/') {
output[outputIndex++] = '\\';
output[outputIndex++] = char;
} else if (uint8(char) < 32) {
// Escape control characters
continue;
} else {
output[outputIndex++] = char;
}
}
bytes memory result = new bytes(outputIndex);
for (uint256 i = 0; i < outputIndex; i++) {
result[i] = output[i];
}
return string(result);
}
  1. Add input length limits:

function mintProfile(
string memory name,
uint8 age,
string memory profileImage
) external {
require(bytes(name).length <= 50, "Name too long");
require(bytes(profileImage).length <= 200, "Image URI too long");
// ... rest of the function
}
  1. Use a JSON library:
    Consider using a tested and audited JSON library for safer JSON string construction.

  2. Implement strict input validation:

function validateProfileInput(string memory input) internal pure returns (bool) {
bytes memory inputBytes = bytes(input);
for (uint i = 0; i < inputBytes.length; i++) {
if (inputBytes[i] == '"' || inputBytes[i] == '\\' || uint8(inputBytes[i]) < 32) {
return false;
}
}
return true;
}
Updates

Appeal created

n0kto Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid_URI_injection_scam_underaged_bad_name_photo_etc

Scamming/phishing is not the protocol problem, that's a user mistake. NFT are unique, even if someone does a copy of your profile (which is also possible in web2), I consider it informational. Injection is a problem for the web2 part of the protocol, not a bug here. For the age, it depends on the countries law and future medicine. Anyways, that's more an ethical/political problem, not a bug.

Support

FAQs

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