Safeguard
AppSec

XSS Script Example: How Cross-Site Scripting Works and How to Stop It

A clear XSS script example shows how untrusted input becomes executable code in a victim's browser. Here is the anatomy of the three XSS types and the defenses that actually work.

Aisha Rahman
Security Analyst
6 min read

A cross-site scripting (XSS) attack happens when an application takes untrusted input and places it into a page in a way that the browser interprets as executable script instead of data, and the simplest XSS script example is a comment field that stores and re-serves <script>...</script> to every visitor. This guide explains the mechanics of each XSS type conceptually and focuses on detection and remediation. It contains no working exploit payloads aimed at real systems, only the minimal illustrations needed to understand why the vulnerability exists.

The core mechanism

Every XSS bug reduces to one confusion: the application fails to keep a clear boundary between the data a user supplied and the code a browser executes. When a value that came from a user ends up in an HTML context without being encoded for that context, the browser has no way to know the difference between content the developer wrote and content an attacker injected.

Consider a page that greets you by name using a query parameter, and inserts it into the page unescaped:

Hello, [value of the "name" parameter]!

If the application drops that value straight into the HTML, an attacker can craft a link whose name parameter is not a name at all but a fragment of markup that closes the current element and opens a script element. The browser parses it as code. Illustratively, the injected fragment looks like <script>/* attacker code */</script> rather than the plain text the developer expected. That is the whole trick: data crossing into a code context.

The three types of XSS

Reflected XSS

The malicious input is included in the immediate response and not stored. The attacker delivers it by getting a victim to click a crafted link, so the payload "reflects" off the server back into that one victim's page. It is per-victim and requires social engineering to land, but it is common because so many pages echo query parameters into their output.

Stored (persistent) XSS

The input is saved server-side, in a comment, profile bio, support ticket, or product review, and then served to everyone who views that record. This is the most damaging type because a single successful injection fires against every subsequent visitor with no further attacker action. The comment-field example above is stored XSS.

DOM-based XSS

The vulnerability lives entirely in client-side JavaScript. The server never sees the payload; instead, front-end code reads something attacker-controllable (the URL fragment, document.referrer, window.name) and writes it into the page using a dangerous sink such as innerHTML or document.write. Because it happens in the browser, server-side logging often misses it entirely.

What attackers do with it

Once script runs in the victim's origin, it can act as the victim: read cookies not marked HttpOnly, exfiltrate session tokens, make authenticated requests to change account settings, keylog form input, or draw a fake login prompt over the real page. XSS is dangerous precisely because the malicious code inherits the trust and privileges of the site it runs on. It is consistently ranked in the OWASP Top 10 under the injection category.

Defense 1: Contextual output encoding

The primary fix is encoding output for the exact context it lands in. The same value needs different treatment depending on where it goes:

  • In HTML body text, encode <, >, &, and quotes to their entity equivalents so the browser renders them as characters, not markup.
  • In an HTML attribute, quote the attribute and encode quotes.
  • In a JavaScript string, use JavaScript-string escaping, not HTML encoding.
  • In a URL, use URL encoding.

Modern frameworks encode by default. React escapes values interpolated into JSX; Angular sanitizes bindings; template engines like Thymeleaf and Razor auto-encode. The bugs appear when developers opt out with dangerouslySetInnerHTML, [innerHTML], v-html, or raw template output. Treat every such opt-out as a place that must handle only already-sanitized content.

Defense 2: A Content Security Policy

A Content Security Policy (CSP) is a defense-in-depth header that tells the browser which script sources to trust. A strict policy refuses to execute inline scripts and blocks scripts from unapproved origins, so even an injected payload frequently fails to run:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-<random>'; object-src 'none'; base-uri 'self'

Using a per-response nonce and eliminating inline event handlers means an attacker's injected inline script has no valid nonce and is blocked. CSP is not a substitute for encoding, it is a safety net for the encoding you missed.

Defense 3: Sanitize rich HTML, harden cookies, validate input

When you genuinely must accept HTML (a rich-text editor, for instance), do not write a regex to strip tags. Use a vetted, allow-list-based sanitizer such as DOMPurify that parses the HTML and removes anything not on an explicit safe list. Additionally:

  • Mark session cookies HttpOnly so injected script cannot read them, and Secure and SameSite to limit theft and misuse.
  • Validate input against expected shape at the boundary, though remember input validation reduces the attack surface but does not replace output encoding.

Detecting XSS before it ships

Finding XSS combines several approaches:

  • Static analysis to trace tainted input from a source (request parameter) to a dangerous sink (unescaped output).
  • Dynamic testing that submits benign marker strings and checks whether they come back unencoded in a script-executing context; the DAST scanner model of probing live endpoints is well suited to reflected and stored XSS.
  • Manual review of every framework escape-hatch (innerHTML, dangerouslySetInnerHTML, v-html) in the codebase.
  • Deploying CSP in report-only mode first to see what would break before enforcing it.

The takeaway from any XSS script example is that the vulnerability is a context problem, not a filtering problem. Keep user data as data, encode it for the exact place it renders, and add CSP as a backstop.

FAQ

What is the simplest way to explain an XSS script example?

An application takes something a user typed, such as a comment, and puts it into a web page without encoding it. The browser then runs the attacker's markup as code instead of showing it as text. The fix is to encode that value for the context it renders in.

What is the difference between reflected and stored XSS?

Reflected XSS is echoed back in the immediate response and must be delivered per victim through a crafted link. Stored XSS is saved on the server and served to every visitor of that record, which makes it more damaging because it requires no further attacker action.

Does a Content Security Policy alone stop XSS?

No. A strict CSP is a strong safety net that can block injected inline scripts, but it does not fix the underlying flaw. Contextual output encoding is the primary defense; CSP is defense-in-depth layered on top of it.

Can input validation prevent XSS on its own?

Not reliably. Input validation reduces the attack surface, but the same value can be safe in one context and dangerous in another. The durable fix is encoding output for the specific context where it renders, plus sanitizing any rich HTML with a vetted allow-list sanitizer.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.