Cross-Site Scripting (XSS)

·4 min read
Cross-Site Scripting (XSS)

Cross-Site Scripting (XSS) is a security vulnerability that allows an attacker to execute malicious JavaScript in another user’s browser under the security context of a trusted website.

XSS usually happens when an application renders dynamic content without treating user input as plain text. Instead of displaying the data, the browser interprets it as HTML or JavaScript and executes it.

Types of XSS

There are three common types of XSS.

DOM-based XSS

DOM-based XSS occurs entirely in the browser. It happens when client-side JavaScript reads untrusted data, such as query parameters, and inserts it into the page using unsafe APIs like innerHTML.

Safe APIs, such as textContent, create text nodes instead of parsing HTML, preventing the browser from interpreting the input as executable code.

Reflected XSS

Reflected XSS occurs when a server immediately includes user input in its response.

A common example is a search page that displays the search query without properly encoding it. An attacker can craft a malicious URL and trick a victim into opening it. The payload is reflected in the response and executed in the victim’s browser.

Stored XSS

Stored XSS is the most dangerous type because the malicious payload is permanently stored, usually in a database.

For example, if a website allows users to post comments and those comments are rendered without proper protection, every visitor who opens the page may execute the attacker’s JavaScript.

Unlike reflected and DOM-based XSS, which usually require a victim to open a specially crafted link, stored XSS affects every user who visits the vulnerable page.

What Can XSS Do?

Because the malicious code runs in the browser under the website’s origin, it can:

  • Read data from the page.
  • Read cookies that are not marked as HttpOnly.
  • Read Local Storage and Session Storage.
  • Send authenticated requests on behalf of the user.
  • Modify the page content.
  • Perform actions as the logged-in user.

Even when cookies are marked as HttpOnly, XSS can still perform authenticated requests because the browser automatically includes cookies in requests to the same website.

Preventing XSS

Output Encoding

Output encoding is the primary defense against XSS.

The idea is simple: convert characters that have special meaning in HTML into their safe representations before rendering them.

For example:

  • < becomes &lt;
  • > becomes &gt;

After encoding, the browser treats the content as plain text instead of HTML, so no new elements or scripts are created.

Encoding depends on where the data is inserted:

  • HTML content → HTML encoding
  • HTML attributes → Attribute encoding
  • URLs → URL encoding
  • JavaScript strings → JavaScript/Unicode encoding

Using the correct encoding for the output context is essential.

Sanitization

Sometimes users are allowed to submit HTML, for example when writing articles or comments.

In these situations, encoding everything would display the HTML instead of rendering it.

Instead, the HTML should be sanitized. A sanitizer removes dangerous content such as:

  • <script> elements
  • Event handler attributes like onclick and onerror
  • Dangerous URLs such as javascript:

The remaining safe HTML can then be rendered.

Content Security Policy (CSP)

Content Security Policy (CSP) is an additional layer of defense that limits which resources a page is allowed to load.

For example, it can restrict:

  • JavaScript
  • Stylesheets
  • Images
  • Fonts
  • Frames

CSP can also:

  • Block inline scripts.
  • Disable eval() and similar APIs.
  • Restrict scripts to trusted origins.

When inline scripts are required, a server can generate a cryptographically secure random nonce for each response. The nonce is included both in the CSP header and in the allowed inline <script> elements. The browser executes only scripts with the matching nonce.

CSP should be considered defense in depth. It reduces the impact of XSS but does not replace proper output encoding and sanitization.

Modern Frameworks

Most modern frameworks automatically protect against XSS by escaping dynamic content by default.

Examples include:

  • ASP.NET Razor
  • React
  • Angular
  • Vue

However, these protections can be bypassed if developers intentionally render raw HTML using APIs such as:

  • innerHTML
  • dangerouslySetInnerHTML
  • Html.Raw()

These APIs should be used only with trusted or properly sanitized content.

Summary

Cross-Site Scripting remains one of the most common web security vulnerabilities.

The most effective defense is to treat all user input as untrusted and use context-appropriate output encoding whenever data is rendered. When HTML must be accepted, sanitize it before rendering. Finally, use Content Security Policy as an additional layer of protection to reduce the impact of potential vulnerabilities.