CVE-2023-44390: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: HtmlSanitizer is a .NET library for cleaning HTML fragments and documents from constructs that can lead to XSS attacks. The vulnerability occurs in configurations where foreign content is allowed, i.e. either `svg` or `math` are in the list of allowed elements. In the case an application sanitizes user input with a vulnerable configuration, an attacker could bypass the sanitization and inject arbitrary HTML, including JavaScript code. Note that in the default configuration the vulnerability is not present. The vulnerability has been fixed in versions 8.0.723 and 8.1.722-beta (preview version).
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-10-05T14:15Z
Repository: https://github.com/mganss/HtmlSanitizer
Language: C#
Files changed: test/HtmlSanitizer.Tests/Tests.cs
Commit date: 2023-10-04 16:26:07 +0200
Commit message: Merge branch 'advisory-fix-1'

Diff: diff --cc test/HtmlSanitizer.Tests/Tests.cs
index 058521f,055f1ee..4513e1e
--- a/test/HtmlSanitizer.Tests/Tests.cs
+++ b/test/HtmlSanitizer.Tests/Tests.cs
@@@ -3486,15 -3486,60 +3486,70 @@@ zqy1QY1kkPOuMvKWvvmFIwClI2393jVVcp91eda
          sanitizer.AllowedAttributes.Add("style");
          var sanitized = sanitizer.Sanitize(html);
          Assert.Equal(html, sanitized);
      }
  
 +    [Fact]
 +    public void Number469Test()
 +    {
 +        // see https://github.com/mganss/HtmlSanitizer/issues/469
 +        var html = @"<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;""/>";
 +        var sanitizer = new HtmlSanitizer();
 +        var sanitized = sanitizer.Sanitize(html);
 +        Assert.Equal(@"<div style=""height: 0; background-image: url(&quot;https://example.com/1.jpg&quot;), url(&quot;https://example.com/2.jpg&quot;), url(&quot;https://example.com/3.jpg&quot;); 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>&lt;/title&gt;&lt;img src=x onerror=alert(1)&gt;</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>&lt;/math&gt;&lt;img src onerror=alert(1)&gt;</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>&lt;/title&gt;&lt;img src=x onerror=alert(1)&gt;</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><!--&lt;/style&gt;&lt;img src=x onerror=alert(1)&gt;--></style></svg>";
+         Assert.Equal(expected, sanitized);
+     }
  }

Fixed code: +
     [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>&lt;/title&gt;&lt;img src=x onerror=alert(1)&gt;</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>&lt;/math&gt;&lt;img src onerror=alert(1)&gt;</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>&lt;/title&gt;&lt;img src=x onerror=alert(1)&gt;</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><!--&lt;/style&gt;&lt;img src=x onerror=alert(1)&gt;--></style></svg>";
         Assert.Equal(expected, sanitized);
     }