HtmlSanitizer vulnerable to Cross-site Scripting in Foreign Content
Description
A bypass in HtmlSanitizer allows XSS when foreign elements (svg/math) and certain raw text elements are allowed, fixed in 8.0.723 and 8.1.722-beta.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A bypass in HtmlSanitizer allows XSS when foreign elements (svg/math) and certain raw text elements are allowed, fixed in 8.0.723 and 8.1.722-beta.
Vulnerability
Overview
CVE-2023-44390 is a cross-site scripting (XSS) bypass vulnerability in the .NET library HtmlSanitizer, which is used to clean HTML fragments and documents of XSS constructs. The flaw manifests in configurations where foreign content elements such as ` or are included in the allowed tags list. When combined with raw text elements like , , or `, an attacker can craft a payload that escapes the sanitizer's processing and injects arbitrary HTML and JavaScript. The default configuration does not permit foreign content and is therefore not affected [1][2].
Exploitation
Mechanism
The attack exploits the way HtmlSanitizer handles the interaction between foreign content namespaces and raw text elements. For instance, if an ` element is allowed, a sequence like can cause the sanitizer to improperly re-open the raw text context, allowing the tag to go unsanitized. The root cause lies in insufficient handling of tag nesting restrictions across MathML/SVG namespaces and raw text elements (such as , , `, and others). No authentication is required, as the vulnerability is triggered entirely through user-supplied HTML processed by the sanitizer [3][4].
Impact
An attacker who can submit HTML that is sanitized by a vulnerable configuration can bypass all protection and inject arbitrary JavaScript or HTML, leading to cross-site scripting (XSS). This can result in session hijacking, data theft, or arbitrary actions performed in the context of the victim user's browser.
Mitigation
The vulnerability is patched in HtmlSanitizer version 8.0.723 and the preview version 8.1.722-beta. Users should upgrade immediately. As a workaround, administrators can disallow foreign elements (` and `) from the allowed tags set, which aligns with the safe default configuration [4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
HtmlSanitizerNuGet | < 8.0.723 | 8.0.723 |
HtmlSanitizerNuGet | >= 8.1.0-beta, < 8.1.722-beta | 8.1.722-beta |
Affected products
2- Range: < 8.0.723
Patches
1ab29319866c0Merge branch 'advisory-fix-1'
2 files changed · +70 −1
src/HtmlSanitizer/HtmlSanitizer.cs+14 −0 modified@@ -453,6 +453,10 @@ private void RemoveComments(INode context) { foreach (var comment in GetAllNodes(context).OfType<IComment>().ToList()) { + var escapedText = comment.TextContent.Replace("<", "<").Replace(">", ">"); + if (escapedText != comment.TextContent) + comment.TextContent = escapedText; + var e = new RemovingCommentEventArgs(comment); OnRemovingComment(e); @@ -463,6 +467,16 @@ private void RemoveComments(INode context) private void DoSanitize(IHtmlDocument dom, IParentNode context, string baseUrl = "") { + // always encode text in raw data content + foreach (var tag in context.QuerySelectorAll("*").Where(t => t.Flags.HasFlag(NodeFlags.LiteralText) && !string.IsNullOrWhiteSpace(t.InnerHtml))) + { + var escapedHtml = tag.InnerHtml.Replace("<", "<").Replace(">", ">"); + if (escapedHtml != tag.InnerHtml) + tag.InnerHtml = escapedHtml; + if (tag.InnerHtml != escapedHtml) // setting InnerHtml does not work for noscript + tag.SetInnerText(escapedHtml); + } + // remove disallowed tags foreach (var tag in context.QuerySelectorAll("*").Where(t => !IsAllowedTag(t)).ToList()) {
test/HtmlSanitizer.Tests/Tests.cs+56 −1 modified@@ -3248,7 +3248,7 @@ public void StyleByPassTest() var sanitized = sanitizer.Sanitize(html, "http://www.example.com"); // Assert - Assert.Equal("aaabc<style>x[x=\"\\3c/style>\\3cimg src onerror=alert(1)>\"] { }</style>", sanitized); + Assert.Equal("aaabc<style>x[x=\"\\3c/style>\\3cimg src onerror=alert(1)>\"] { }</style>", sanitized); } [Fact] @@ -3497,4 +3497,59 @@ public void Number469Test() var sanitized = sanitizer.Sanitize(html); Assert.Equal(@"<div style=""height: 0; background-image: url("https://example.com/1.jpg"), url("https://example.com/2.jpg"), url("https://example.com/3.jpg"); display: none""></div>", sanitized); } + + [Fact] + public void BypassTest() + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedTags.Add("svg"); + sanitizer.AllowedTags.Add("title"); + sanitizer.AllowedTags.Add("xmp"); + var bypass = @"<svg></p><title><xmp></title><img src=x onerror=alert(1)></xmp></title>"; + var sanitized = sanitizer.Sanitize(bypass, "https://www.example.com"); + var expected = @"<svg><p></p><title><xmp></title><img src=x onerror=alert(1)></xmp></title></svg>"; + Assert.Equal(expected, sanitized); + } + + [Fact] + public void Bypass2Test() + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedTags.Add("form"); + sanitizer.AllowedTags.Add("math"); + sanitizer.AllowedTags.Add("mtext"); + sanitizer.AllowedTags.Add("mglyph"); + sanitizer.AllowedTags.Add("xmp"); + var bypass = @"<form><math><mtext></form><form><mglyph><xmp></math><img src onerror=alert(1)>"; + var sanitized = sanitizer.Sanitize(bypass, "https://www.example.com"); + var expected = @"<form><math><mtext><form><mglyph><xmp></math><img src onerror=alert(1)></xmp></mglyph></form></mtext></math></form>"; + Assert.Equal(expected, sanitized); + } + + [Fact] + public void Bypass3Test() + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedTags.Add("svg"); + sanitizer.AllowedTags.Add("title"); + sanitizer.AllowedTags.Add("noscript"); + var bypass = @"<svg></p><title><noscript></title><img src=x onerror=alert(1)></noscript></title>"; + var sanitized = sanitizer.Sanitize(bypass, "https://www.example.com"); + var expected = "<svg><p></p><title><noscript></title><img src=x onerror=alert(1)></noscript></title></svg>"; + Assert.Equal(expected, sanitized); + } + + [Fact] + public void Bypass4Test() + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedTags.Add("svg"); + sanitizer.AllowedTags.Add("p"); + sanitizer.AllowedTags.Add("style"); + sanitizer.RemovingComment += (s, e) => e.Cancel = true; + var bypass = @"<svg></p><style><!--</style><img src=x onerror=alert(1)>-->"; + var sanitized = sanitizer.Sanitize(bypass, "https://www.example.com"); + var expected = "<svg><p></p><style><!--</style><img src=x onerror=alert(1)>--></style></svg>"; + Assert.Equal(expected, sanitized); + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-43cp-6p3q-2pc4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-44390ghsaADVISORY
- github.com/mganss/HtmlSanitizer/commit/ab29319866c020f0cc11e6b92228cd8039196c6eghsax_refsource_MISCWEB
- github.com/mganss/HtmlSanitizer/security/advisories/GHSA-43cp-6p3q-2pc4ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.