FreeFungi

Challenges FF-002

Gobstopper CWE-79 A03:2021

Sticky Label

Reflected XSS in the search echo.

Search for a sweet and the results heading says “3 results for fudge”, echoing your search term back onto the page. If that echo isn’t escaped, whatever you type is rendered as HTML, and that includes a <script> tag. This is a reflected cross-site scripting flaw: your input bounces straight off the server and runs in the browser.

New to this? What is cross-site scripting (XSS)?

Cross-site scripting, or XSS, is when a site puts your input onto a page without cleaning it up, so the browser runs it as code instead of showing it as text. A <script> tag you typed actually executes.

This one is reflected XSS: the payload bounces straight back off a single request, like a search term echoed into the results heading. The fix is to escape output so tags become harmless text.

Need the shop running? One line:

Start the shop
docker run -p 3000:3000 ghcr.io/bad-adventure/freefungi

1 · Find it

Search for something with a tag in it, like <b>hello</b>. If the word hello comes back bold instead of showing the angle brackets as text, the page is treating your input as markup. That is the whole game: the shop is building HTML out of your search term without escaping it first.

2 · Exploit it

Swap the harmless tag for something that runs. Paste this into the search box:

<script>alert(document.domain)</script>

The alert fires. Because the payload is reflected out of the URL, you can put it in a link and it runs in the browser of anyone who clicks:

http://localhost:3000/search?q=<script>alert(document.domain)</script>

If a naive filter strips <script>, an event handler does the same job and slips past it:

<img src=x onerror="alert(document.cookie)">

Why it matters: a crafted link is enough to run script in a victim’s session. Chain it with a stolen session cookie and you are acting as them, no password required.

3 · Understand it

The results heading is rendered from your search term without escaping. In the template it is printed with the raw-output tag rather than the escaping one, so the browser parses your input as HTML:

<!-- the raw-output tag (<%- %>) prints your input as HTML -->
<h2><%- searchTerm %></h2>

<!-- so this input... -->
?q=<script>alert(1)</script>

<!-- ...becomes a live <script> element in the page -->

4 · Fix it

Escape the value before it reaches the page, so < and > become harmless entities. In EJS that is simply the escaping tag (<%= %>) instead of the raw one (<%- %>):

✗ Vulnerable
<!-- raw: renders input as HTML -->
<h2><%- searchTerm %></h2>
✓ Patched
<!-- escaped: renders input as text -->
<h2><%= searchTerm %></h2>

Now <script>alert(1)</script> shows up on the page as literal text, which is exactly what a search term should do.

The general rule: escape output for the context it lands in. HTML body, HTML attribute, JavaScript and URL each need different escaping. Most template engines escape by default, so the bug usually creeps in the moment someone reaches for the “raw” or “unsafe” version to make some markup work.