Vulnerabilities/Cross-Site Scripting
Cross-Site Scripting
Cross-site scripting happens when a page includes text from one user in a way the browser parses as HTML or JavaScript, so it runs for other users with the site's own authority. The fix is the same everywhere: encode every value for the exact place it is written, sanitize the rare fields that must allow HTML, and back both with a Content Security Policy. Pick your language below for the exact APIs, vulnerable and fixed code, and the CI rules that catch it.
Plain English Explainer (ELI5)
A newspaper prints readers' letters on its own pages. One reader writes a letter laid out exactly like a notice from the editor: 'Subscribers: call this number and read out your card details to renew.' Printed in the paper's own typeface, readers trust it as if the editor wrote it. XSS is a website printing a visitor's text into its own page, where the browser trusts it as the site's own code, with the site's access to your account. Escaping prints every letter inside a clearly marked quotation box, so no letter can pass as an editor's notice.
Core Concepts & Subsystem Terms
- Cross-site scripting (CWE-79)
- A web page includes untrusted data without neutralizing it for the browser, so markup or script in that data is parsed and executed in the page's origin, with access to its DOM, cookies and APIs.
- Reflected, stored and DOM-based XSS
- Reflected XSS echoes request data straight back in the response, usually via a crafted link. Stored XSS saves the value and serves it to every viewer. DOM-based XSS never touches the server: client-side JavaScript copies data from the URL or storage into a sink such as
innerHTML. - Contextual output encoding
- Each output context needs its own encoding: HTML entities for element text and quoted attributes, JavaScript string escaping inside scripts, percent-encoding inside URL parameters, and a scheme check for
hrefandsrc. HTML-encoding a value placed inside a script is not enough. - Sanitizing vs encoding
- Encoding shows markup as text. When users must submit formatting, run the HTML through an allowlist sanitizer (DOMPurify, bleach's successor nh3, OWASP Java HTML Sanitizer, HtmlSanitizer, ammonia) that keeps permitted tags and removes scripts, event handlers and
javascript:URLs. - Content Security Policy (CSP)
- A response header that tells the browser which scripts may run. A nonce-based
script-srcwithout'unsafe-inline'blocks most injected scripts even when an encoding bug slips through, andrequire-trusted-types-for 'script'locks down DOM sinks.
Step-by-Step Attack Flow
Untrusted text reaches a page
A search term, profile field, comment, URL fragment or API response is included when the page is built on the server or updated in the browser.
It is written without the right encoding
Template escaping is turned off, the value goes into a script or URL context that HTML escaping does not cover, or client code assigns it to innerHTML. The benign test value <img src=x onerror=alert(1)> survives intact.
The browser parses it as code
The victim's browser builds a real <img> element and fires onerror, or runs a javascript: link when clicked.
The script acts as the site
It runs with the site's origin: it can read the page, call the site's APIs with the victim's cookies or tokens, change what the user sees and submit forms on their behalf.
XSS guides by language
Engineering & System Hardening Checklist
- Render every page through a template engine with autoescaping on, and review every opt-out (
|safe,Html.Raw,dangerouslySetInnerHTML,raw,unsafe { },{!! !!}). - Encode for the exact context: HTML text and quoted attributes, JavaScript strings, URL parameters; never build HTML or JavaScript by string concatenation.
- Sanitize user-supplied HTML with a maintained allowlist sanitizer at the point it is rendered.
- Allow only
httpandhttpsin user-supplied URLs before they reachhref,srcor navigation. - Send a nonce-based Content-Security-Policy without
'unsafe-inline', plusX-Content-Type-Options: nosniff. - Keep session tokens in
HttpOnly,Secure,SameSite=Laxcookies rather thanlocalStorage.