Cross-Site Scripting (XSS)

Cross-Site Scripting (XSS) is one of the most widespread and damaging vulnerabilities in web application security. It allows an attacker to inject malicious scripts into a web page, which then execute in the browsers of other users. The consequences range from stolen session tokens and credential harvesting to full account takeover. Although the full name includes “Cross-Site,” the abbreviation is XSS rather than CSS because “CSS” was already established as the acronym for Cascading Style Sheets.

Fundamentally, XSS is a failure of trust boundaries. An application accepts untrusted user input and renders it directly into a page without adequate sanitization or context-aware encoding. The browser cannot distinguish legitimate application markup from attacker-supplied code, so it executes the injected script as though it originated from the trusted site itself.
A typical vulnerable pattern looks like this:

<p>Search results: <?php echo $_GET['keyword']; ?></p>

If an attacker supplies <script>alert(1)</script> as the keyword parameter, the server returns that markup unchanged. The victim’s browser then parses and runs the script. This simple act of echoing unsanitized input into the HTML response is the root cause of most XSS flaws.

The Three Main Types of XSS

Stored XSS

Stored XSS, also called persistent XSS, occurs when the malicious payload is permanently saved on the server—usually in a database—and later delivered to other users who request the affected page. Common injection points include:

  • Comment sections and guestbooks
  • User profiles and display names
  • Forum posts and private messages

Because the payload lives on the server, every visitor who views the compromised content is a potential victim. This makes stored XSS the most dangerous variant in terms of scale and impact.

Reflected XSS

Reflected XSS happens when the malicious script is embedded in a request parameter, typically in a URL query string, and the server immediately “reflects” it back in the response page. The attacker must convince a victim to click a crafted link:

https://example.com/search?q=<script>alert(document.cookie)</script>

This type is usually short-lived, affecting only the user who activates the malicious link. It depends heavily on social engineering, such as phishing emails or malicious advertisements.

DOM-Based XSS

DOM-based XSS exists entirely on the client side. The server may never encounter the malicious payload at all. Instead, client-side JavaScript reads data from sources such as location.hash, document.URL, or document.referrer, and then writes it into the page using unsafe APIs:

document.getElementById('tip').innerHTML = location.hash.substring(1);

Because the dangerous flow occurs in the browser, server-side logs may show nothing unusual, making this variant particularly difficult to detect through traditional server monitoring.

Potential Consequences of XSS

A successful XSS attack can trigger serious security incidents:

  • Session hijacking: The script reads document.cookie and sends session tokens to an attacker-controlled server, allowing the attacker to impersonate the victim.
  • Keylogging: Malicious code can intercept keystrokes and capture passwords, credit card numbers, or other sensitive input.
  • Phishing and page defacement: Attackers can rewrite page content, inject fake login forms, or redirect users to malicious sites.
  • Worm propagation: On social platforms, XSS payloads can self-replicate and spread virally. The historic Samy worm on MySpace is a well-known example.
  • Cross-Site Request Forgery (CSRF) chaining: With the victim’s authenticated browser executing attacker-controlled scripts, the attacker can forge state-changing requests such as fund transfers or account updates.

Core Defense Strategies

Output Encoding: The Primary Line of Defense

The most effective protection against XSS is to encode untrusted data before inserting it into a page, using the encoding scheme appropriate to the output context:

  • HTML body: Apply HTML entity encoding to characters such as <, >, &, ", and '.
  • JavaScript contexts: Serialize data as JSON and escape special characters before embedding it in script blocks.
  • URL attributes and query strings: Use functions like encodeURIComponent to sanitize dynamic URL components.

Input Validation

Validate user input against strict allowlists—for example, confirming that an email field matches a proper email pattern. However, input validation is complementary, not a substitute for output encoding. The same piece of data may be rendered in multiple contexts, and a filter that is safe for one context may be insufficient for another.

Safe APIs and Modern Frameworks

Prefer DOM APIs that treat content as plain text rather than executable markup:

// Unsafe
element.innerHTML = userInput;

// Safe
element.textContent = userInput;

Modern frontend frameworks such as React and Vue automatically escape interpolated values by default, which significantly reduces the risk of accidental XSS. Developers should still exercise caution when using escape hatches like dangerouslySetInnerHTML or v-html.

Content Security Policy (CSP)

A robust Content Security Policy restricts the origins from which a page may load scripts, styles, and other resources. Delivered via an HTTP response header, CSP can prevent the execution of inline scripts and block external code from untrusted domains:

Content-Security-Policy: default-src 'self'; script-src 'self'

Even if an attacker manages to inject a script tag, the browser will refuse to execute code that violates the policy.

Session cookies should carry attributes that limit their accessibility:

Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Lax

The HttpOnly flag prevents JavaScript from reading the cookie, which directly mitigates session theft via document.cookie. Secure ensures the cookie is only sent over HTTPS, and SameSite=Lax helps protect against cross-site request leakage.

Conclusion

Defending against XSS requires a layered approach rather than a single silver bullet. Teams should adopt defense-in-depth by encoding all untrusted output, validating input, using safe APIs, enforcing CSP, and hardening session cookies. Security should also be embedded in the development lifecycle through code reviews, automated scanning, and regular penetration testing.

Ultimately, the golden rule of web security applies: never trust user input. By treating all external data as potentially hostile and applying consistent, context-aware sanitization, developers can dramatically reduce the risk of XSS and protect their users from compromise.