The clearest XSS attack examples all share one root cause: untrusted input reaches the browser as executable markup instead of inert text. Cross-site scripting (XSS) happens when an application takes data a user controls and writes it into an HTML page without neutralizing the characters that a browser treats as code. Once that boundary breaks, an attacker's script runs in the victim's session with the victim's privileges.
This guide explains the attack conceptually and shows illustrative, defanged snippets so you can recognize the pattern in your own code. There are no live payloads here aimed at real targets, just the shapes to look for and the fixes that close them.
The three classes of XSS
XSS splits into three categories, and most real-world XSS attack examples fall cleanly into one of them.
Reflected XSS bounces the payload off the server in a single response. A user clicks a crafted link, the server echoes part of the URL back into the page, and the script executes immediately. Stored XSS is more dangerous because the payload is saved somewhere (a comment, a profile field, a support ticket) and served to everyone who views it later. DOM-based XSS never touches the server logic at all; client-side JavaScript reads something attacker-influenced and writes it into the DOM unsafely.
Knowing which class you are dealing with tells you where to look: the response template, the datastore write/read path, or the client bundle.
Reflected XSS: the search box classic
Imagine a search results page that greets the user with their query. A naive server-side template does something like this:
<p>Results for: <%= request.query.q %></p>
If q contains markup, the browser parses it as part of the document. An attacker crafts a link where the query parameter carries a script element instead of a word, sends it to a victim, and the injected code runs when the page renders. The payload lives entirely in the URL, so nothing is stored.
The fix is contextual output encoding. Before writing user data into HTML, encode the characters that have structural meaning so < becomes <, > becomes >, and quotes become their entity forms. Every mainstream template engine has an auto-escaping mode; the bug almost always comes from a developer bypassing it with a raw-output helper.
Stored XSS: the poisoned comment
Stored XSS shows up when persisted content is rendered without sanitization. Picture a product review saved verbatim to a database and later rendered into every visitor's page:
// Vulnerable render path
reviewEl.innerHTML = review.body;
Any reviewer who submits markup instead of plain text plants a script that fires for every future reader. Because it is served from your own trusted origin, it can read cookies not marked HttpOnly, make authenticated requests, or rewrite the page to phish credentials.
Two controls matter here. First, sanitize rich text on input with a vetted library (DOMPurify on the client, or a server-side HTML sanitizer) that strips dangerous elements and attributes while keeping the formatting you want. Second, prefer textContent over innerHTML whenever the value should be plain text:
// Safe when the body is meant to be text, not HTML
reviewEl.textContent = review.body;
DOM-based XSS: when the client betrays you
DOM-based XSS is the sneakiest of the XSS attack examples because a server-side scan of your templates finds nothing. The tainted flow lives in the browser. A common pattern reads the URL fragment and injects it straight into the page:
// Vulnerable: location.hash is attacker-influenced
document.getElementById("welcome").innerHTML =
"Hi " + decodeURIComponent(location.hash.slice(1));
Sources like location.hash, location.search, document.referrer, and postMessage data are attacker-influenced. Sinks like innerHTML, outerHTML, document.write, and eval execute what you hand them. DOM XSS is a source flowing into a sink without sanitization.
Modern frameworks help a lot here. React, Vue, and Angular escape interpolated values by default, which is why the vulnerable cases usually involve an escape hatch such as React's dangerouslySetInnerHTML or Angular's bypassSecurityTrustHtml. Treat every use of those as a code-review checkpoint.
Detecting XSS at scale
You will not catch every case by reading code. A layered detection strategy works better:
Static analysis (SAST) traces tainted data from source to sink and flags unescaped output. Dynamic testing exercises the running app with a battery of encoded probes and watches whether they execute; a DAST scanner is well suited to reflected and stored XSS because it sees the rendered response the way a browser does. Manual review remains valuable for DOM-based flows where framework escape hatches are involved.
For third-party risk, a sanitizer or framework you depend on can itself carry an XSS bypass. An SCA tool such as Safeguard can flag when a version of a library like DOMPurify in your tree is affected by a known bypass advisory, which is a class of exposure code review will never surface on its own.
Hardening beyond the code
Even with clean output encoding, add defense in depth. A Content Security Policy (CSP) that disallows inline scripts turns many injection attempts into no-ops, because an injected script element without a valid nonce simply will not run. Mark session cookies HttpOnly so script cannot read them, and Secure so they never cross plaintext. Set the SameSite attribute to blunt the cross-site request angle.
None of these replace encoding and sanitization, but they raise the cost of an exploit and shrink the blast radius when one input slips through. If you want a structured path through these controls, the Safeguard Academy walks through building a CSP that does not break your own front end.
FAQ
What is the difference between reflected and stored XSS?
Reflected XSS delivers the payload in a single request-response cycle, usually through a crafted URL, and does not persist. Stored XSS saves the payload on the server so it executes for every user who later views the affected page, which makes it more damaging and harder to clean up.
Does using React or Angular make me immune to XSS?
No. Those frameworks escape interpolated values by default, which removes the most common mistakes, but they all provide escape hatches like dangerouslySetInnerHTML that reintroduce the risk. DOM-based XSS from unsafe use of these APIs is still common.
Is output encoding or input validation the right fix for XSS?
Contextual output encoding is the primary defense because it neutralizes data at the moment it enters HTML. Input validation is a useful supporting control that rejects obviously bad data early, but it should never be your only line because the same input can be safe in one context and dangerous in another.
Can a Content Security Policy alone stop XSS?
A strict CSP significantly reduces the impact of XSS, especially by blocking inline scripts, but it is a mitigation rather than a cure. Attackers occasionally find CSP bypasses, so encoding and sanitization must still be correct at the source.