GHSA-9mrh-v2v3-xpfm: sanitize-html allowedTags Bypass via Entity-Decoded Text in nonTextTags Elements
SummaryCommit 49d0bb7 introduced a regression in sanitize-html that bypasses `allowedTags` enforcement for text inside `nonTextTagsArray` elements (`textarea` and `option`). Entity-encoded HTML inside these elements passes through the sanitizer as decoded, unescaped HTML, allowing injection of arbitrary tags including XSS payloads. This affects any application using sanitize-html that includes `option` or `textarea` in its `allowedTags` configuration.DetailsThe vulnerable code is at `packages/sanitize-html/index.js:569-573`:The comment is factually incorrect. htmlparser2 10.x **does** decode HTML entities inside both `<textarea>` and `<option>` elements before passing text to the `ontext` callback. This can be verified:Because the code assumes the text is "already properly encoded" and skips `escapeHtml()`, the decoded entities (`<`, `>`) are written directly to the output as literal HTML characters. This completely bypasses the `allowedTags` filter — any tag can be injected inside an allowed `option` or `textarea` element using entity encoding.The execution flow:Attacker submits: `<option>&lt;img src=x onerror=alert(1)&gt;</option>`htmlparser2 parses and decodes entities → `ontext` receives `<img src=x onerror=alert(1)>`Code at line 569 checks: tag is `option`, which is in `nonTextTagsArray` → trueLine 573: `result += text` — writes decoded text directly without escapingOutput: `<option><img src=x onerror=alert(1)></option>` — `<img>` tag injected despite not being in `allowedTags`The `script` and `style` tags are handled separately at lines 563-568 (before the vulnerable block), so the effective vulnerability applies to `textarea` and `option`, plus any custom elements added to `nonTextTags` by the user.Prior to commit 49d0bb7, text in these elements fell through to the `escapeHtml` branch (line 574-580), which correctly re-encoded the decoded entities.PoC**Prerequisites:** Application using sanitize-html 2.17.2 with `option` or `textarea` in `allowedTags`.**Step 1: Basic tag injection via option****Step 2: Element breakout with XSS event handler****Step 3: Textarea breakout (also vulnerable)****Step 4: Full select/option context breakout**All outputs verified against sanitize-html 2.17.2 with htmlparser2 10.x.Impact**Complete `allowedTags` bypass**: Any HTML tag can be injected through an allowed `option` or `textarea` element using entity encoding, defeating the core security guarantee of sanitize-html.**Stored XSS**: Applications that sanitize user-submitted HTML and allow `option` or `textarea` tags (common in form builders, CMS platforms, rich text editors) are vulnerable to stored cross-site scripting.**Session hijacking**: Attackers can inject event handlers (`onerror`, `onload`, etc.) to steal session cookies or authentication tokens.**Scope**: Affects non-default configurations only — the default `allowedTags` does not include `option` or `textarea`. However, these tags are commonly allowed in applications that handle form-related HTML content.Recommended FixRemove the vulnerable code block at lines 569-573 entirely. The `escapeHtml` branch (line 574) correctly handles these elements — htmlparser2 10.x decodes entities, and re-encoding with `escapeHtml` produces correct HTML output (entities are round-tripped, not double-encoded).This fix restores the pre-49d0bb7 behavior where all non-script/style text content goes through `escapeHtml()`, ensuring decoded entities are properly re-encoded before output.
CVSS v3.1: 6.1