Cross-Site Scripting (XSS): Types, Attacks, and Defenses
Cross-site scripting, commonly abbreviated XSS, is a web vulnerability that lets an attacker execute malicious scripts in another user's browser in the context of a trusted site. Because the script runs with the victim's privileges, it can steal session tokens, perform actions on the user's behalf, capture keystrokes, or rewrite page content. XSS remains one of the most prevalent web weaknesses, and understanding its variants is essential for building safe front-end and back-end code.
How Cross-Site Scripting Works
At its core, XSS happens when an application takes data that an attacker controls and places it into an HTML page without proper encoding or sanitization. The browser cannot tell the difference between script the developer intended and script an attacker injected, so it executes both. The injected code runs within the site's origin, which means it can reach cookies (unless protected), the DOM, and any data the user can see.
The attacker's goal is usually to get JavaScript to run. That might mean injecting a <script> tag, an event handler such as onerror, or a javascript: URL, depending on where the input lands in the page.
Impact of a Successful XSS Attack
Because injected script executes with the same authority as the page itself, the consequences reach well beyond a defaced page. An attacker can read and exfiltrate data rendered on the page, submit forms as the victim, and pivot to other actions the user is authorized to perform. If the site stores authentication material somewhere reachable by script, the attacker can hijack the session and impersonate the user entirely. This is why XSS is treated as a high-severity flaw rather than a cosmetic bug, and why the payload location matters as much as its content.
Types of XSS
Reflected XSS
In reflected XSS, the malicious input is included in a response immediately, typically through a URL parameter or form field. The victim must be lured into clicking a crafted link. Because the payload is not stored, each attack targets a specific user who follows the link.
Stored XSS
Stored, or persistent, XSS occurs when the malicious input is saved by the application, for example in a comment, profile field, or support ticket, and later served to other users. This is more dangerous because it can affect every visitor who views the poisoned content without any special link.
DOM-Based XSS
DOM-based XSS lives entirely in client-side code. It happens when JavaScript reads attacker-controllable data, such as the URL fragment, and writes it into a dangerous sink like innerHTML or document.write. The server may never see the payload, so server-side filtering alone cannot catch it.
Defending Against XSS
No single control stops every case, so layered defenses are recommended.
Context-Aware Output Encoding
The primary defense is to encode untrusted data for the exact context where it is rendered. HTML body, HTML attributes, JavaScript, CSS, and URLs each require different escaping. Most templating engines encode HTML by default, which prevents the majority of reflected and stored XSS when used consistently.
Safe DOM APIs
Prefer APIs that treat input as text rather than markup. Assigning to textContent is safe, whereas assigning untrusted data to innerHTML is not.
// Vulnerable: untrusted input is parsed as HTML
element.innerHTML = location.hash.slice(1);
// Safer: input is inserted as plain text, not markup
element.textContent = location.hash.slice(1);
Input Validation and Sanitization
Validate input against expected formats, and when you must allow rich HTML, run it through a well-maintained sanitizer that removes scripts and dangerous attributes. Never build your own blocklist of tags; attackers know countless bypasses.
Content Security Policy
A strong Content Security Policy acts as a safety net by restricting which scripts the browser will execute, so even an injected payload may fail to run. Combine it with nonces or hashes and avoid unsafe-inline.
Protecting Session Data
Mark session cookies as HttpOnly so injected scripts cannot read them directly, as covered in secure cookies. This does not stop XSS but limits token theft. Note that XSS and CSRF are distinct issues, and defenses for one do not fully cover the other.
Framework Features and Trusted Types
Web frameworks provide automatic escaping and opt-in protections such as Trusted Types, which force dangerous DOM sinks to accept only vetted values. Enabling these features closes many DOM-based gaps.
A Practical Prevention Checklist
- Encode output based on its rendering context, and rely on your framework's auto-escaping.
- Avoid dangerous sinks like
innerHTML,eval, anddocument.writewith untrusted data. - Sanitize any user-supplied HTML with a trusted library.
- Deploy a restrictive Content Security Policy with nonces or hashes.
- Set
HttpOnlyand other protective attributes on session cookies. - Test continuously with both automated scanners and manual review in authorized environments.
Key Takeaways
- Cross-site scripting executes attacker-controlled scripts in a victim's browser within a trusted site's origin.
- The three main types are reflected, stored, and DOM-based, and DOM-based XSS requires client-side fixes.
- Context-aware output encoding is the primary defense, backed by safe DOM APIs and input sanitization.
- A strong Content Security Policy and HttpOnly cookies reduce impact when other controls fail.
- Layered defenses and continuous authorized testing provide the most reliable protection.