Summary
The NFTSVG library generates SVG images for NFTs by concatenating strings based on input parameters. However, the library does not sanitize the input parameters before embedding them into the SVG output. This could potentially allow an attacker to inject malicious code into the SVG if user-supplied input is not properly handled.
Proof of Concept (PoC)
A user provides the following malicious input for the status parameter:
<script>alert('XSS');</script>
The generateSVG function is called with this input:
function generateSVG(SVGParams memory params) internal pure returns (string memory) {
(vars.statusWidth, vars.statusCard) =
SVGElements.card({ cardType: SVGElements.CardType.STATUS, content: params.status });
return string.concat(
'<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">',
SVGElements.BACKGROUND,
generateDefs(params.accentColor, params.status, vars.cards),
generateFloatingText(params.sablierAddress, params.sablierModel, params.assetAddress, params.assetSymbol),
generateHrefs(vars.progressXPosition, vars.statusXPosition, vars.amountXPosition, vars.durationXPosition),
"</svg>"
);
}
The resulting SVG contains the malicious script:
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
<!-- other SVG content -->
<script>alert('XSS');</script>
</svg>
When this SVG is rendered by a browser, the script executes, resulting in an XSS attack.
Impact
If an attacker can control the input parameters (such as accentColor, amount, assetAddress, assetSymbol, duration, progress, sablierAddress, sablierModel, and status), they could inject malicious SVG content. This content could include JavaScript, which might be executed by a client's browser when the SVG is rendered, leading to Cross-Site Scripting (XSS) attacks. Such attacks could result in the theft of cookies, session tokens, or other sensitive information from the client's browser.
Tools Used
Manual review
Recommendations
Implement comprehensive input sanitization for all parameters that will be included in the SVG output. This can be done by encoding special characters or using a whitelist approach to allow only safe characters.
Sanitized Input Example:
function sanitizeInput(string memory input) internal pure returns (string memory) {
bytes memory inputBytes = bytes(input);
for (uint256 i = 0; i < inputBytes.length; i++) {
if (inputBytes[i] == '<' || inputBytes[i] == '>' || inputBytes[i] == '&' || inputBytes[i] == '"') {
inputBytes[i] = ' ';
}
}
return string(inputBytes);
}
function generateSVG(SVGParams memory params) internal pure returns (string memory) {
params.accentColor = sanitizeInput(params.accentColor);
params.amount = sanitizeInput(params.amount);
params.assetAddress = sanitizeInput(params.assetAddress);
params.assetSymbol = sanitizeInput(params.assetSymbol);
params.duration = sanitizeInput(params.duration);
params.progress = sanitizeInput(params.progress);
params.sablierAddress = sanitizeInput(params.sablierAddress);
params.sablierModel = sanitizeInput(params.sablierModel);
params.status = sanitizeInput(params.status);
SVGVars memory vars;
(vars.statusWidth, vars.statusCard) =
SVGElements.card({ cardType: SVGElements.CardType.STATUS, content: params.status });
vars.cards = string.concat(vars.progressCard, vars.statusCard, vars.amountCard, vars.durationCard);
return string.concat(
'<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">',
SVGElements.BACKGROUND,
generateDefs(params.accentColor, params.status, vars.cards),
generateFloatingText(params.sablierAddress, params.sablierModel, params.assetAddress, params.assetSymbol),
generateHrefs(vars.progressXPosition, vars.statusXPosition, vars.amountXPosition, vars.durationXPosition),
"</svg>"
);
}
To mitigate SVG injection risks, it is crucial to sanitize user-supplied inputs before using them in SVG generation. The sanitizeInput function provided above replaces potentially harmful characters with spaces.