What Is Cross-Site Scripting (XSS)?
Cross-Site Scripting (XSS) is one of the most prevalent security vulnerabilities in web applications. Consistently featured in the OWASP Top 10, XSS allows attackers to inject malicious JavaScript code into web pages. This code executes in the victim's browser, enabling attacks such as cookie theft, session hijacking, phishing, and even malware distribution.
According to 2024 data, XSS accounts for approximately 18% of all web security vulnerabilities. This percentage demonstrates that XSS remains one of the most serious web security threats today.
Types of XSS Attacks
1. Stored XSS (Persistent XSS)
Stored XSS is the most dangerous type of XSS attack. The attacker stores the malicious code directly in the web application's database. This code is automatically executed in the browser of every user who visits the affected page.
Common targets for Stored XSS:
- Blog comments and forum posts
- User profile information
- Product reviews
- Messaging systems
- Support tickets
Why is Stored XSS the most dangerous?
- No direct interaction between attacker and victim is required
- All users visiting the page are affected
- Malicious code is permanently stored
- Can be difficult to detect
2. Reflected XSS
In Reflected XSS, the malicious code is sent to the server through URL parameters, form inputs, or HTTP headers and is reflected directly in the server response. The attacker must trick the victim into clicking a specially crafted link.
Reflected XSS attack vectors include:
- Search result pages
- Error messages
- URL parameters
- Form fields (reflected server-side)
- HTTP headers
3. DOM-Based XSS
DOM XSS occurs entirely on the client side without any changes on the server. It emerges when JavaScript code manipulates the DOM in an unsafe manner. Since it leaves no trace in server logs, it is the most difficult XSS type to detect.
Dangerous JavaScript sources that can lead to DOM XSS:
document.URLdocument.locationdocument.referrerwindow.namelocation.hash
Dangerous sinks:
innerHTMLdocument.write()eval()setTimeout()/setInterval()element.setAttribute()(with event handlers)
XSS Attack Types Comparison
| Characteristic | Stored XSS | Reflected XSS | DOM XSS |
|---|---|---|---|
| Where is the malicious code? | Database | URL/request | Client-side |
| Server interaction | Yes | Yes | No |
| Victim interaction required | Only page visit | Click on link | Click on link |
| Scope of impact | All visitors | Single user | Single user |
| Detection difficulty | Medium | Easy | Hard |
| Severity level | Very High | High | High |
Input Sanitization
Input sanitization is the first line of defense against XSS. All user input must be treated as potentially dangerous.
Server-Side Validation
- Whitelist approach: Define allowed characters and reject everything else
- Data type validation: Accept only numeric input for numeric fields
- Length limitations: Limit input length to reasonable bounds
- Regex validation: Format validation for fields like email and phone
- HTML sanitization: Use libraries like DOMPurify or Bleach to remove dangerous tags
Client-Side Validation
Client-side validation improves user experience but is never sufficient for security purposes. Server-side validation must always be performed as well.
Security rule: Never rely on client-side validation alone. Client-side checks can be easily bypassed. Server-side validation must always be the primary line of defense.
Output Encoding
Output encoding replaces dangerous characters with their safe equivalents when displaying user input. The correct encoding method must be selected based on the context:
HTML Encoding
When displaying user data in an HTML context, HTML entity encoding must be applied:
<is encoded as<>is encoded as>&is encoded as&"is encoded as"'is encoded as'
JavaScript Encoding
When user data is used in a JavaScript context, JavaScript encoding must be applied. The JSON.stringify() function can be used for this purpose but is only safe within a JSON data structure.
URL Encoding
When user data appears in URL parameters, URL encoding (percent-encoding) must be applied. The encodeURIComponent() function serves this purpose.
CSS Encoding
Using user data in CSS contexts should be avoided whenever possible. When necessary, CSS encoding must be applied.
Content Security Policy (CSP)
Content Security Policy is one of the most powerful defense mechanisms against XSS. CSP is an HTTP header that tells the browser which sources of content are allowed to load.
CSP Directives
| Directive | Description | Example |
|---|---|---|
| default-src | Default source policy | 'self' |
| script-src | JavaScript source restriction | 'self' 'nonce-abc123' |
| style-src | CSS source restriction | 'self' 'unsafe-inline' |
| img-src | Image source restriction | 'self' data: https: |
| connect-src | AJAX/WebSocket restriction | 'self' api.example.com |
| frame-src | iframe source restriction | 'none' |
| object-src | Plugin source restriction | 'none' |
CSP Nonce and Hash
To allow inline scripts to execute, use the nonce or hash approach:
- Nonce: A unique random value is generated per page load and added to both the CSP header and the script tag
- Hash: The hash of the allowed inline script is specified in the CSP header
CSP Reporting
You can monitor CSP violations using the report-uri or report-to directives. Initially, use the Content-Security-Policy-Report-Only header to test your policy without enforcing it.
Additional Security Headers
Beyond CSP, additional HTTP security headers provide protection against XSS:
- X-Content-Type-Options: nosniff — Prevents MIME type sniffing
- X-Frame-Options: DENY — Prevents clickjacking attacks
- Referrer-Policy: strict-origin-when-cross-origin — Limits referrer information leakage
- Permissions-Policy — Restricts access to browser features
- Strict-Transport-Security — Enforces HTTPS usage
Framework-Specific Protections
React
React applies output encoding by default in JSX. However, caution is required when using dangerouslySetInnerHTML. This prop injects HTML directly into the DOM and can introduce XSS vulnerabilities.
Angular
Angular treats all values as untrusted by default and applies automatic sanitization. Exercise extreme caution when using functions like bypassSecurityTrustHtml().
ASP.NET Core
ASP.NET Core applies HTML encoding by default in Razor views. Using @Html.Raw() creates an XSS risk and should be avoided whenever possible.
XSS Testing Tools
- Burp Suite: Comprehensive web security scanning tool
- OWASP ZAP: Free and open-source security scanner
- XSS Hunter: Specialized tool for blind XSS detection
- DOM Invader: Burp Suite's DOM XSS testing module
- CSP Evaluator: Google's CSP policy evaluation tool
Only perform security testing on your own applications and with proper legal authorization. Unauthorized security testing can have legal consequences.
XSS Prevention Checklist
- Validate all user input on the server side
- Apply context-appropriate output encoding
- Configure a strict Content Security Policy
- Use HttpOnly and Secure cookie flags
- Do not disable your framework's built-in protections
- Configure security headers
- Conduct regular security scans
- Keep your dependencies up to date
- Implement security training and awareness programs
- Create a security incident response plan
Conclusion
Protection against XSS attacks requires a layered security approach rather than a single measure. Input sanitization, output encoding, Content Security Policy, and security headers together form an effective defense. Keep your framework's built-in protections active, conduct regular security scans, and train your team on secure coding practices. Remember, security is a continuous process, and your defense strategies must constantly evolve in parallel with emerging threats.