← All posts
5 min read

Cross-Site Scripting: When Untrusted Content Becomes Browser Code

#technology#security#xss#web
📑 On this page

Web pages combine data and executable languages.

If attacker-controlled content is inserted into HTML, JavaScript, CSS, or a URL using the wrong API, the browser may interpret data as code under the site's identity.

Prevent XSS by keeping untrusted content as data and encoding it for the exact output context.

One generic "escape" function cannot safely handle every browser context.

A concrete example: a comment

A comment contains markup with a script-capable event.

If the application inserts it as raw HTML, another user's browser may execute attacker-controlled code.

If the application assigns the same text through a safe text API, the browser displays the characters instead of interpreting them.

Why XSS matters

Injected code runs in the origin of the vulnerable site.

It may:

  • perform actions as the user,
  • read accessible page data,
  • alter forms,
  • capture entered secrets,
  • send requests,
  • or spread through stored content.

HttpOnly cookies reduce cookie theft but do not prevent actions using the active session.

Stored XSS

Stored XSS persists attacker content:

  • comment,
  • profile,
  • support ticket,
  • product description,
  • or uploaded filename.

Every viewer may execute it until the content is removed or output is fixed.

Administrative dashboards are high-value targets because they view user-supplied data.

Reflected XSS

Reflected XSS places request input directly into the immediate response.

An attacker sends a crafted link whose query value becomes executable content. The victim must usually follow the link.

Safe templating and context-aware output encoding prevent request data from changing page structure.

DOM-based XSS

DOM XSS occurs in client-side JavaScript.

Code reads a source such as:

  • URL fragment,
  • query string,
  • message event,
  • local storage,

and sends it into an unsafe sink such as raw HTML insertion or dynamic script execution.

The server response may contain no malicious value.

HTML text context

When displaying ordinary text, use APIs that create text nodes or framework interpolation that escapes by default.

Do not switch to raw HTML because styling appears convenient.

Know the framework's escape hatches and review every use.

HTML attribute context

Attribute values need correct quoting and encoding.

Some attributes are inherently dangerous:

  • event handlers,
  • srcdoc,
  • style,
  • and URL-bearing attributes.

Prefer setting known safe properties through structured APIs rather than building markup strings.

JavaScript context

Do not insert untrusted text into inline JavaScript.

Serializing data into a script block requires a trusted serializer that handles closing tags and special characters. Better, deliver data through JSON or DOM attributes and let code read it as data.

JavaScript string escaping is not HTML escaping.

URL context

Validate allowed schemes.

An apparently valid link can use a script-capable or unexpected scheme. Restrict to needed protocols such as https and construct URLs through a URL API.

Encoding a dangerous scheme does not make it safe.

CSS context

User-controlled CSS can leak data, overlay interfaces, or use dangerous browser features depending on context.

Avoid injecting raw CSS. Offer structured style choices such as approved colors or classes.

CSS sanitization is specialized work.

Rich text

Some products intentionally allow limited HTML.

Use a mature HTML sanitizer with an allow list of:

  • elements,
  • attributes,
  • URL schemes,
  • and nesting.

Sanitize on a trusted boundary and keep the library updated as browser behavior evolves.

Encoding versus sanitization

Encoding displays content literally in one output context.

Sanitization removes or transforms dangerous parts while preserving permitted markup.

Use encoding for plain text and sanitization only when rich content is a real requirement.

Framework protection

Modern frameworks escape interpolated text by default.

Risk appears through:

  • raw HTML directives,
  • direct DOM APIs,
  • unsafe URL assignment,
  • third-party components,
  • or bypass functions.

Document and lint escape-hatch usage.

Content Security Policy

CSP restricts which scripts and other resources a page may execute.

A strong policy can reduce XSS impact by:

  • disallowing inline script,
  • using nonces or hashes,
  • restricting script origins,
  • and reporting violations.

CSP is defense in depth, not a replacement for safe output.

Trusted Types

Trusted Types can require dangerous DOM sinks to receive values created by approved policies.

This helps large applications find and control raw HTML and script injection paths.

It requires browser support and careful policy design.

Cookies

Use:

  • HttpOnly,
  • Secure,
  • SameSite,
  • and narrow scope.

HttpOnly prevents JavaScript from reading a cookie but an XSS payload can still make authenticated requests and read page-visible data.

Cookie flags limit consequences; they do not cure XSS.

Third-party scripts

A third-party script runs with substantial page access.

Limit providers, review necessity, pin or integrity-check static assets where possible, and monitor supply-chain risk.

Loading a script intentionally grants it trust similar to first-party code.

Testing

Test every output context with representative hostile strings.

Use:

  • unit tests for encoding,
  • browser tests,
  • static analysis for unsafe sinks,
  • CSP reports,
  • and security review.

Do not rely only on searching for the word script; many execution paths use other markup.

Knowledge check

  1. How do stored, reflected, and DOM XSS differ?
  2. Why is one generic escaping function insufficient?
  3. When is sanitization appropriate instead of encoding?
  4. How does CSP reduce risk?
  5. Why does HttpOnly not fully protect a session from XSS?

The one idea to remember

XSS occurs when attacker-controlled content reaches an executable browser context. Use safe text and structured APIs, encode for the exact context, sanitize only necessary rich HTML, restrict dangerous sinks, and layer CSP and cookie controls around the primary fix.