Skip to content

Instantly share code, notes, and snippets.

@irsdl
Created June 1, 2026 16:01
Show Gist options
  • Select an option

  • Save irsdl/da6a691f45283a4987447e9efb1d434d to your computer and use it in GitHub Desktop.

Select an option

Save irsdl/da6a691f45283a4987447e9efb1d434d to your computer and use it in GitHub Desktop.
XSS via Regex Bypass in `sanitizeHtmlWithStylePreservation`

Summary

A Cross-Site Scripting (XSS) vulnerability exists due to a flawed regular expression in the sanitizeHtmlWithStylePreservation function. The regex /<style[\s\S]*?<\/style>/gi used to extract and preserve style tags can be bypassed in two ways, allowing arbitrary HTML and JavaScript to evade DOMPurify sanitization entirely. This results in full XSS execution.

Details

The vulnerability exists in search-parts/src/services/templateService/TemplateService.ts (lines 716-745):

public sanitizeHtmlWithStylePreservation(html: string): string {
    if (!html) return html;

    const styleTags: string[] = [];
    let templateWithoutStyles = html;
    const styleRegex = /<style[\s\S]*?<\/style>/gi;  // VULNERABLE REGEX
    let match;

    while ((match = styleRegex.exec(html)) !== null) {
      styleTags.push(match[0]);
      templateWithoutStyles = templateWithoutStyles.replace(
        match[0],
        `<div data-style-placeholder="${styleTags.length - 1}"></div>`
      );
    }

    let sanitized = DomPurifyHelper.sanitize(templateWithoutStyles);

    // Restore all style tags - INCLUDING ANY XSS PAYLOAD INSIDE
    let restoredTemplate = sanitized;
    styleTags.forEach((styleTag, index) => {
      restoredTemplate = restoredTemplate.replace(
        `<div data-style-placeholder="${index}"></div>`,
        styleTag  // VULNERABILITY: Unsanitized content re-inserted
      );
    });

    return restoredTemplate;
}

The regex has two critical flaws:

Flaw Pattern Issue Bypass Example
Closing tag not strict <\/style> requires exact match but browsers accept </style x> <style>...</style x><img src=x onerror=alert(1)></style>
Opening tag not bounded <style matches <stylefoobar but browsers don't recognize it as a style tag <stylefoobar>...<img src=x onerror=alert(2)></style>

PoC

The PoC should potentially be testable by creating or editing a Handlebars template in a PnP Modern Search web part. However, I have used the jsFiddle website to simulate the used DOMPurify settings to prove existence of these security issues.

Bypass 1 - Malformed closing tag:

<style>body { color: red; }</style x><img src=x onerror=alert(document.domain)></style>

Bypass 2 - Invalid tag name:

<stylefoobar>body { color: red; }<img src=x onerror=alert(document.cookie)></style>

Impact

  • Full XSS Execution: Arbitrary JavaScript runs in the context of the SharePoint site - no user interaction required
  • Session Hijacking: Steal session cookies, OAuth tokens, and authentication credentials
  • Data Theft: Access any SharePoint data the victim user can access
  • Account Takeover: Perform actions as the victim user (modify permissions, upload malware, etc.)
  • Privilege Escalation: A Site Owner could attack Global Admins viewing the page

Recommended Fix

const styleRegex = /<style(?:\s[^>]*)?>[\s\S]*?<\/style[^>]*>/gi;

Patch

Patched in https://github.com/microsoft-search/pnp-modern-search/releases/tag/4.21.0

microsoft-search/pnp-modern-search#4659

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment