DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

Unescaped name and image in tokenURI allow JSON metadata injection

Root + Impact

Description

  • tokenURI builds on-chain JSON metadata from the profile name, age and image.

  • name and profileImage are user input and are concatenated into the JSON without escaping. A name containing " can close the string and add or override keys (e.g. a fake description, extra attributes, or a different image). Marketplaces and front-ends that render this metadata show attacker-controlled fields as if they came from the protocol.

abi.encodePacked(
'{"name":"',
@> profileName, // unescaped user input
'", ',
'"description":"A soulbound dating profile NFT.", ',
...
'"image":"',
@> imageURI, // unescaped user input
'"}'
)

Example: minting with the name x", "description":"Verified by DatingDapp team produces

{"name":"x", "description":"Verified by DatingDapp team", "description":"A soulbound dating profile NFT.", "attributes": [...], "image":"ipfs://e"}

Many JSON parsers keep the first duplicate key, so the fake description is what users see.

Risk

Likelihood:

  • Any user can choose their name and image when minting; no privileges needed.

Impact:

  • Spoofed metadata (fake verification text, fake attributes, swapped image) enables impersonation and phishing on a dating platform; broken JSON for names containing quotes or backslashes. No direct fund loss.

Proof of Concept

Test setup: owner deploys SoulboundProfileNFT and LikeRegistry(address(nft)); alice, bob, carol each get 10 ETH and mint a profile (token ids 1, 2, 3). Full file test/AuditPoC.t.sol, forge test --mc AuditPoC passes.

function test_F5_metadataInjection() public {
address eve = makeAddr("eve");
vm.prank(eve);
nft.mintProfile('x", "description":"Verified by DatingDapp team', 30, "ipfs://e");
string memory json = string(abi.encodePacked(
'{"name":"x", "description":"Verified by DatingDapp team", ',
'"description":"A soulbound dating profile NFT.", ',
'"attributes": [{"trait_type": "Age", "value": 30}], ',
'"image":"ipfs://e"}'));
assertEq(nft.tokenURI(nft.profileToToken(eve)), Base64.encode(bytes(json)));
}

Recommended Mitigation

Validate or escape user strings before building JSON:

function mintProfile(string memory name, uint8 age, string memory profileImage) external {
+ require(_isSafe(name) && _isSafe(profileImage), "Invalid characters");
...
}
+
+ function _isSafe(string memory s) internal pure returns (bool) {
+ bytes memory b = bytes(s);
+ for (uint256 i; i < b.length; i++) {
+ if (b[i] == '"' || b[i] == '\\' || uint8(b[i]) < 0x20) return false;
+ }
+ return true;
+ }
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 8 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!