DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Unescaped Special Characters in Token Symbols Can Lead to Invalid SVG Generation

Summary

The MetadataImage contract generates SVG images for NFTs, incorporating token symbols. However, it doesn't escape special characters in these symbols, potentially resulting in invalid SVG output. This can cause rendering issues or complete failure to display the NFT image correctly.

Vulnerability Details

https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/4e0ad0b964f74a1b4880114f4dd5b339bc69cd3e/protocol/contracts/beanstalk/metadata/MetadataImage.sol#L647-L655

In the tokenName and getTokenName functions, the contract directly inserts the token symbol into the SVG XML without escaping special characters:

function tokenName(address token) internal view returns (string memory) {
return
string(
abi.encodePacked(
'<text x="10" y="14.5" font-size="12" fill="White" text-anchor="start" font-family="futura">',
getTokenName(token),
"</text>"
)
);
}
function getTokenName(address token) internal view returns (string memory tokenString) {
if (token == C.UNRIPE_LP) {
tokenString = "urBEANLP";
} else {
tokenString = ERC20(token).symbol();
}
}

If a token's symbol contains special XML characters like <, >, &, ', or ", it will break the SVG structure, potentially causing rendering issues or making the SVG invalid.

Impact

  1. NFT images may fail to render correctly or at all in various SVG viewers or browsers.

  2. Important token information (symbol) might be missing or displayed incorrectly.

  3. User experience degradation due to broken or improperly rendered NFT images.

  4. Potential confusion or misinformation about the nature of the NFT due to missing or incorrect token symbols.

  5. Possible issues with third-party platforms or marketplaces that process these NFTs.

  6. Reputational damage to the project due to consistently broken or incorrectly rendered NFTs.

Tools Used

Recommendations

  1. Implement an XML/SVG escaping function:

function escapeXML(string memory input) internal pure returns (string memory) {
bytes memory inputBytes = bytes(input);
bytes memory output = new bytes(inputBytes.length * 6);
uint256 outputLength = 0;
for (uint256 i = 0; i < inputBytes.length; i++) {
if (inputBytes[i] == '&') {
bytes memory escaped = "&amp;";
for (uint256 j = 0; j < escaped.length; j++) {
output[outputLength++] = escaped[j];
}
} else if (inputBytes[i] == '<') {
bytes memory escaped = "&lt;";
for (uint256 j = 0; j < escaped.length; j++) {
output[outputLength++] = escaped[j];
}
} else if (inputBytes[i] == '>') {
bytes memory escaped = "&gt;";
for (uint256 j = 0; j < escaped.length; j++) {
output[outputLength++] = escaped[j];
}
} else if (inputBytes[i] == '"') {
bytes memory escaped = """;
for (uint256 j = 0; j < escaped.length; j++) {
output[outputLength++] = escaped[j];
}
} else if (inputBytes[i] == "'") {
bytes memory escaped = "&#39;";
for (uint256 j = 0; j < escaped.length; j++) {
output[outputLength++] = escaped[j];
}
} else {
output[outputLength++] = inputBytes[i];
}
}
bytes memory result = new bytes(outputLength);
for (uint256 i = 0; i < outputLength; i++) {
result[i] = output[i];
}
return string(result);
}
  1. Modify the getTokenName function to use this escaping function:

function getTokenName(address token) internal view returns (string memory tokenString) {
if (token == C.UNRIPE_LP) {
tokenString = "urBEANLP";
} else {
tokenString = escapeXML(ERC20(token).symbol());
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational/Gas

Invalid as per docs https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

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