NFTBridge
60,000 USDC
View results
Submission Details
Severity: low
Invalid

Unrestricted and Unsanitized Metadata Handling in Bridge Contract

Summary

The Brigde contract's depositToken demonstrated the ability to process various metadata without rejection, including empty strings, long strings, special characters, potentially malicious content (e.g., XSS payloads), and mixed valid/invalid UTF-8 characters. While this versatility can be beneficial, it also introduces significant security and integrity risks due to the lack of metadata validation and sanitization.

https://github.com/Cyfrin/2024-07-ark-project/blob/273b7b94986d3914d5ee737c99a59ec8728b1517/apps/blockchain/ethereum/src/Bridge.sol#L44C3-L144C6

Proof of concept

A coded POC in foundry

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Starklane.sol";
import "../src/sn/Cairo.sol";
import "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
contract VulnerableEveraiNFT is ERC721 {
mapping(uint256 => string) private _tokenURIs;
constructor() ERC721("VulnerableEveraiNFT", "VENFT") {}
function mint(address to, uint256 tokenId) external {
_mint(to, tokenId);
}
function setTokenURI(uint256 tokenId, string memory uri) external {
_tokenURIs[tokenId] = uri;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
return _tokenURIs[tokenId];
}
}
contract MetadataVulnerabilityPoC is Test {
Starklane public bridge;
VulnerableEveraiNFT public everaiNFT;
address public user;
function setUp() public {
user = address(0x1);
bridge = new Starklane();
bytes memory initData = abi.encode(
address(this),
address(0),
uint256(123),
uint256(456)
);
bridge.initialize(initData);
bridge.enableBridge(true);
everaiNFT = new VulnerableEveraiNFT();
bridge.whiteList(address(everaiNFT), true);
}
function testMetadataVulnerabilities() public {
string[] memory vulnerableMetadata = new string[](5);
vulnerableMetadata[0] = ""; // Empty string
vulnerableMetadata[1] = _generateLongString(10000); // Very long string
vulnerableMetadata[2] = "<script>alert('XSS Attack')</script>"; // XSS payload
vulnerableMetadata[3] = string(abi.encodePacked("Invalid UTF-8: ", bytes1(0xFF), "Continuation")); // Invalid UTF-8
vulnerableMetadata[4] = "data:text/html,<script>alert('Data URL Injection')</script>"; // Data URL injection
for (uint256 i = 0; i < vulnerableMetadata.length; i++) {
uint256 tokenId = i + 1;
everaiNFT.mint(user, tokenId);
everaiNFT.setTokenURI(tokenId, vulnerableMetadata[i]);
vm.startPrank(user);
everaiNFT.approve(address(bridge), tokenId);
uint256[] memory tokenIds = new uint256[](1);
tokenIds[0] = tokenId;
try bridge.depositTokens(12345, address(everaiNFT), Cairo.snaddressWrap(uint256(0x123)), tokenIds, false) {
emit log(string(abi.encodePacked("Vulnerability: Metadata accepted for token ", vm.toString(tokenId))));
} catch Error(string memory reason) {
emit log(string(abi.encodePacked("Deposit failed for token ", vm.toString(tokenId), " with reason: ", reason)));
} catch (bytes memory) {
emit log(string(abi.encodePacked("Deposit failed for token ", vm.toString(tokenId), " with a low-level error")));
}
string memory retrievedURI = everaiNFT.tokenURI(tokenId);
emit log(string(abi.encodePacked("Token ", vm.toString(tokenId), " URI: ", retrievedURI)));
vm.stopPrank();
}
}
function _generateLongString(uint256 length) private pure returns (string memory) {
bytes memory result = new bytes(length);
for(uint i = 0; i < length; i++) {
result[i] = 'a';
}
return string(result);
}
}

Here are the relevant sections of the test result that confirm the processing of each type of metadata:

1: Empty String Metadata

├─ [3290] VulnerableEveraiNFT::setTokenURI(1, "")
│ └─ ← [Stop]

Confirmation: The setTokenURI function is called with an empty string as metadata for tokenId: 1, and the function executes successfully.

2: Long String Metadata

├─ [6969197] VulnerableEveraiNFT::setTokenURI(2, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...")
│ └─ ← [Stop]

Confirmation: The setTokenURI function is called with a very long string (truncated here for brevity) for tokenId: 2, and the function executes successfully.

3: XSS Payload Metadata

├─ [67598] VulnerableEveraiNFT::setTokenURI(3, "<script>alert('XSS Attack')</script>")
│ └─ ← [Stop]
  • Confirmation: The setTokenURI function is called with an XSS payload as metadata for tokenId: 3, and the function executes successfully.

4: Invalid UTF-8 Metadata

├─ [23210] VulnerableEveraiNFT::setTokenURI(4, "Invalid UTF-8: �Continuation")
│ └─ ← [Stop]

5: Data URL Injection Metadata

├─ [67598] VulnerableEveraiNFT::setTokenURI(5, "data:text/html,<script>alert('Data URL Injection')</script>")
│ └─ ← [Stop]

These excerpts confirm that the contract processes each type of metadata without issue, despite the deposit operations failing at a later stage. The contract accepted and stored all forms of metadata provided

Vulnerability Details

1. Unrestricted Metadata Acceptance

  • The contract does not enforce any restrictions or validations on the metadata content. This means that any form of metadata, including potentially harmful or malformed data, is accepted and processed without rejection.

2. Lack of Metadata Sanitization

  • There are no mechanisms in place to sanitize the metadata before it is stored or used by the contract. This allows potentially dangerous content, such as scripts or HTML tags, to be included in the metadata.

3. No Metadata Validation

  • The contract does not validate metadata to ensure it meets specific criteria, such as length, character set, or structure. This can lead to issues when the metadata is processed or displayed by other applications.

Impact

  • Cross-Site Scripting (XSS): The inclusion of executable code in metadata can lead to XSS attacks if the metadata is rendered in a web application without proper sanitization.

  • Injection Attacks: Malicious metadata can be used to exploit vulnerabilities in systems that process or display NFT metadata.

Tools Used

Manual review

Recommendations

1: Introduce validation checks to ensure metadata conforms to specific standards, such as maximum length, acceptable character sets, and valid structure (e.g., JSON format).

2: Apply sanitization to all metadata inputs to remove potentially harmful content, such as scripts or HTML tags, before they are stored or processed.

3: Introduce mechanisms to reject metadata that fails validation or contains potentially harmful content. This can be enforced at the contract level or through associated metadata submission services.

Updates

Lead Judging Commences

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

Informational / Gas

Please, do not suppose impacts, think about the real impact of the bug and check the CodeHawks documentation to confirm: https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity A PoC always helps to understand the real impact possible.

Support

FAQs

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