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
Response:
Search results for: "test"
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><" '
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><" '"
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
Request:
GET /search?q=test3030%3E%3C%22+%27&search-button=&lang=en_US HTTP/2
Host: [REDACTED]
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>
🔍 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
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
Response:
pushMCView(["trackPageView",{
"search":"</script>test3030><\" '"
}]);
flushViewQueue();
} catch (e) { console.error(e); }
</script>
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:
- close the existing
<script>tag with</script> - inject an SVG element (lightweight, reliable)
- use its
onloadevent to run JavaScript
Final payload:
</script><svg/onload=alert(document.domain)>
Full malicious URL:
/search?q=</script><svg/onload=alert(document.domain)>&search-button=&lang=en_US
Sent it through Burp and inspected the response:
pushMCView(["trackPageView",{
"search":"</script><svg/onload=alert(document.domain)><\" '"
}]);
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)>
How it appears in the HTML:
<script>
pushMCView(["trackPageView",{"search":"</script> ← script ends HERE
<svg/onload=alert(document.domain)> ← this becomes HTML!
"}]);
</script>
What the browser actually parses:
- start of a
<script>block - some JS:
pushMCView(["trackPageView",{"search":" - closing tag
</script>→ browser thinks the script is done - HTML content
<svg/onload=alert(document.domain)>→ this executes - leftover text
"}]);→ browser ignores it - 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)