code-423n4 / 2024-06-panoptic-findings

1 stars 0 forks source link

Protocol is vulnerable to SVG JSON injection attacks #21

Closed howlbot-integration[bot] closed 1 month ago

howlbot-integration[bot] commented 1 month ago

Lines of code

https://github.com/code-423n4/2024-06-panoptic/blob/153f0d82440b7e63075d55b0659706531431145f/contracts/base/FactoryNFT.sol#L58-L118

Vulnerability details

Proof of Concept

Take a look at https://github.com/code-423n4/2024-06-panoptic/blob/153f0d82440b7e63075d55b0659706531431145f/contracts/base/FactoryNFT.sol#L58-L118

    function constructMetadata(
        address panopticPool,
        string memory symbol0,
        string memory symbol1,
        uint256 fee
    ) public view returns (string memory) {
        uint256 lastCharVal = uint160(panopticPool) & 0xF;
        uint256 rarity = PanopticMath.numberOfLeadingHexZeros(panopticPool);

        string memory svgOut = generateSVGArt(lastCharVal, rarity);

        svgOut = generateSVGInfo(svgOut, panopticPool, rarity, symbol0, symbol1);
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(
                        bytes(
                            abi.encodePacked(
                                '{"name":"',
                                abi.encodePacked(
                                    LibString.toHexString(uint256(uint160(panopticPool)), 20),
                                    "-",
                                    string.concat(
                                        metadata[bytes32("strategies")][lastCharVal].dataStr(),
                                        "-",
                                        LibString.toString(rarity)
                                    )
                                ),
                                '", "description":"',
                                string.concat(
                                    "Panoptic Pool for the ",
                                    symbol0,
                                    "-",
                                    symbol1,
                                    "-",
                                    PanopticMath.uniswapFeeToString(uint24(fee)),
                                    " market"
                                ),
                                '", "attributes": [{',
                                '"trait_type": "Rarity", "value": "',
                                string.concat(
                                    LibString.toString(rarity),
                                    " - ",
                                    metadata[bytes32("rarities")][rarity].dataStr()
                                ),
                                '"}, {"trait_type": "Strategy", "value": "',
                                metadata[bytes32("strategies")][lastCharVal].dataStr(),
                                '"}, {"trait_type": "ChainId", "value": "',
                                getChainName(),
                                '"}]',
                                '", "image": "',
                                "data:image/svg+xml;base64,",
                                Base64.encode(bytes(svgOut)),
                                '"}'
                            )
                        )
                    )
                )
            );
    }

This function is used to construct the metadata and returns the metadata URI for a given set of characteristics, under which the SVGs are being generated via generateSVGInfo & generateSVGArt(), issue however is that there is no sanitzation of input data done in anywhere while generating these SVGs, considering both generateSVGInfo() &generateSVGArt(), just ingest whatever data is available from querying even the symbols of the token via which an injection attack could be placed, see https://github.com/code-423n4/2024-06-panoptic/blob/153f0d82440b7e63075d55b0659706531431145f/contracts/base/FactoryNFT.sol#L124-L177

    function generateSVGArt(
        uint256 lastCharVal,
        uint256 rarity
    ) internal view returns (string memory svgOut) {
        svgOut = metadata[bytes32("frames")][
            rarity < 18 ? rarity / 3 : rarity < 23 ? 23 - rarity : 0
        ].decompressedDataStr();
        svgOut = svgOut.replace(
            "<!-- LABEL -->",
            write(
                metadata[bytes32("strategies")][lastCharVal].dataStr(),
                maxStrategyLabelWidth(rarity)
            )
        );

        svgOut = svgOut
            .replace(
                "<!-- TEXT -->",
                metadata[bytes32("descriptions")][lastCharVal + 16 * (rarity / 8)]
                    .decompressedDataStr()
            )
            .replace("<!-- ART -->", metadata[bytes32("art")][lastCharVal].decompressedDataStr())
            .replace("<!-- FILTER -->", metadata[bytes32("filters")][rarity].decompressedDataStr());
    }

    /// @notice Fill in the pool/rarity specific text fields on the SVG artwork.
    /// @param svgIn The SVG artwork to complete
    /// @param panopticPool The address of the Panoptic Pool
    /// @param rarity The rarity of the NFT
    /// @param symbol0 The symbol of `token0` in the Uniswap pool
    /// @param symbol1 The symbol of `token1` in the Uniswap pool
    /// @return The final SVG artwork with the pool/rarity specific text fields filled in
    function generateSVGInfo(
        string memory svgIn,
        address panopticPool,
        uint256 rarity,
        string memory symbol0,
        string memory symbol1
    ) internal view returns (string memory) {
        svgIn = svgIn
            .replace("<!-- POOLADDRESS -->", LibString.toHexString(uint160(panopticPool), 20))
            .replace("<!-- CHAINID -->", getChainName());

        svgIn = svgIn.replace(
            "<!-- RARITY_NAME -->",
            write(metadata[bytes32("rarities")][rarity].dataStr(), maxRarityLabelWidth(rarity))
        );

        return
            svgIn
                .replace("<!-- RARITY -->", write(LibString.toString(rarity)))
                .replace("<!-- SYMBOL0 -->", write(symbol0, maxSymbolWidth(rarity)))
                .replace("<!-- SYMBOL1 -->", write(symbol1, maxSymbolWidth(rarity)));
    }

Impact

The process of generating the SVGs is vulnerable to the popular JSON injection attack, considering no validation is being done on the data to be attached to the SVGs and external data re queried to set it up like the token's symobl() which are all windows as to how this attack would be processed.

Recommended Mitigation Steps

Always sanitize the input data

Assessed type

Context

Picodes commented 1 month ago

Out of scope with the scoping Q&A

c4-judge commented 1 month ago

Picodes marked the issue as unsatisfactory: Out of scope