Security: blog body sanitization uses bypassable regex (stored XSS) — replace with HtmlSanitizer #674

Closed
opened 2026-07-17 18:27:29 +00:00 by spikerj · 1 comment
Owner

Stored XSS: blog body HTML is sanitized with bypassable regex. (found via S1135 TODO triage)

BlogContentValidator.SanitizeHtmlBody (SpikerSoft.Business/Domain/Blog/Validation) is applied to user-submitted request.Body in CreateBlogPostCommandHandler and UpdateBlogPostCommandHandler, then stored and rendered as blog HTML (the method deliberately allows 'safe HTML'). Its own comment flags the gap:

// TODO: In production, use HtmlSanitizer library for proper HTML sanitization

Why the current regex approach is exploitable (OWASP explicitly warns against regex HTML sanitization):

  • It removes only <script>...</script> PAIRS, inline on*= handlers, javascript: and data: substrings.
  • It strips no dangerous tags<iframe>, <object>, <embed>, <svg>, <math>, <base>, <link>, <meta>, <form> all pass through. e.g. <iframe src="//evil">, <svg onload=...>, <math href=...>.
  • The javascript: filter is defeated by HTML-entity encoding the browser still decodes: <a href="java&#115;cript:alert(1)">.
  • Event-handler regex is defeated by whitespace/newline tricks and by re-introduction after a single non-recursive pass; mutation-XSS via malformed tags sidesteps regex entirely.

Impact: an authenticated blog author (or anyone who can reach these endpoints) can store markup that executes JavaScript in every viewer's browser (stored XSS) — session theft, CSRF-token exfiltration, account takeover of readers/admins.

Fix: replace the regex logic in SanitizeHtmlBody with a real allowlist-based sanitizer — the developer-intended HtmlSanitizer (Ganss.Xss), which parses the DOM (AngleSharp) and allowlists safe tags/attributes/URL schemes, defeating the above classes of bypass. Configure a blog-appropriate allowlist (headings, p, strong/em, lists, links with http/https/mailto only, blockquote, code/pre, img with http/https src). Keep the existing spam/link checks. Add XSS-bypass regression tests (iframe, svg/onload, entity-encoded javascript:, mutation vectors).

Severity: HIGH (stored XSS on a rendered content surface). SonarQube surfaced the TODO as csharpsquid:S1135; the underlying issue is the sanitizer design.

**Stored XSS: blog body HTML is sanitized with bypassable regex.** (found via S1135 TODO triage) `BlogContentValidator.SanitizeHtmlBody` (SpikerSoft.Business/Domain/Blog/Validation) is applied to user-submitted `request.Body` in **CreateBlogPostCommandHandler** and **UpdateBlogPostCommandHandler**, then stored and rendered as blog HTML (the method deliberately allows 'safe HTML'). Its own comment flags the gap: > // TODO: In production, use HtmlSanitizer library for proper HTML sanitization **Why the current regex approach is exploitable** (OWASP explicitly warns against regex HTML sanitization): - It removes only `<script>...</script>` PAIRS, inline `on*=` handlers, `javascript:` and `data:` substrings. - It strips **no dangerous tags** — `<iframe>`, `<object>`, `<embed>`, `<svg>`, `<math>`, `<base>`, `<link>`, `<meta>`, `<form>` all pass through. e.g. `<iframe src="//evil">`, `<svg onload=...>`, `<math href=...>`. - The `javascript:` filter is defeated by HTML-entity encoding the browser still decodes: `<a href="java&#115;cript:alert(1)">`. - Event-handler regex is defeated by whitespace/newline tricks and by re-introduction after a single non-recursive pass; mutation-XSS via malformed tags sidesteps regex entirely. **Impact:** an authenticated blog author (or anyone who can reach these endpoints) can store markup that executes JavaScript in every viewer's browser (stored XSS) — session theft, CSRF-token exfiltration, account takeover of readers/admins. **Fix:** replace the regex logic in SanitizeHtmlBody with a real allowlist-based sanitizer — the developer-intended HtmlSanitizer (Ganss.Xss), which parses the DOM (AngleSharp) and allowlists safe tags/attributes/URL schemes, defeating the above classes of bypass. Configure a blog-appropriate allowlist (headings, p, strong/em, lists, links with http/https/mailto only, blockquote, code/pre, img with http/https src). Keep the existing spam/link checks. Add XSS-bypass regression tests (iframe, svg/onload, entity-encoded javascript:, mutation vectors). Severity: HIGH (stored XSS on a rendered content surface). SonarQube surfaced the TODO as csharpsquid:S1135; the underlying issue is the sanitizer design.
Author
Owner

Resolved in spikersoft-backend PR #373 (merged to master, reviewed by spikerj). Replaced the bypassable regex blog-body sanitization with the allowlist/DOM-based HtmlSanitizer (Ganss.Xss 9.0.892), restricted URL schemes to http/https/mailto, dropped interactive form controls, and added XSS-bypass regression tests (dangerous tags, svg onload, entity-encoded javascript:) plus a preserve-safe-formatting test. Closing.

Resolved in spikersoft-backend PR #373 (merged to master, reviewed by spikerj). Replaced the bypassable regex blog-body sanitization with the allowlist/DOM-based HtmlSanitizer (Ganss.Xss 9.0.892), restricted URL schemes to http/https/mailto, dropped interactive form controls, and added XSS-bypass regression tests (dangerous tags, svg onload, entity-encoded javascript:) plus a preserve-safe-formatting test. Closing.
Sign in to join this conversation.