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.
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> |
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>- JSFiddle demo: https://jsfiddle.net/0uda1ymj/
Bypass 2 - Invalid tag name:
<stylefoobar>body { color: red; }<img src=x onerror=alert(document.cookie)></style>- JSFiddle demo: https://jsfiddle.net/0uda1ymj/1/
- 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
- Use the recommendations at https://github.com/microsoft-search/pnp-modern-search/security/advisories/GHSA-vhgq-xhm4-6rg5 in order to manage styles.
- Change the Regular Expression to use the following:
const styleRegex = /<style(?:\s[^>]*)?>[\s\S]*?<\/style[^>]*>/gi;
Patched in https://github.com/microsoft-search/pnp-modern-search/releases/tag/4.21.0