CVE-2017-16016: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Sanitize-html is a library for scrubbing html input of malicious values. Versions 1.11.1 and below are vulnerable to cross site scripting (XSS) in certain scenarios: If allowed at least one nonTextTags, the result is a potential XSS vulnerability.
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: 2018-06-04T19:29Z
Repository: https://github.com/punkave/sanitize-html
Language: JavaScript
Files changed: README.md, index.js, test/test.js
Commit date: 2016-03-28 11:09:11 -0400
Commit message: Fixed XSS attack vector via `textarea` tags (when explicitly allowed). Decided that `script` (obviously) and `style` (due to its own XSS vectors) cannot realistically be afforded any XSS protection if allowed, unless we add a full CSS parser. Thanks again to Andrew Krasichkov.

Diff: diff --git a/README.md b/README.md
index f1a50c9..68cb117 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,11 @@
 
 `sanitize-html` is tolerant. It is well suited for cleaning up HTML fragments such as those created by ckeditor and other rich text editors. It is especially handy for removing unwanted CSS when copying and pasting from Word.
 
 `sanitize-html` allows you to specify the tags you want to permit, and the permitted attributes for each of those tags.
 
-If a tag is not permitted, the contents of the tag are still kept, except for script and style tags.
+If a tag is not permitted, the contents of the tag are still kept, except for `script`, `style` and `textarea` tags.
 
 The syntax of poorly closed `p` and `img` elements is cleaned up.
 
 `href` attributes are validated to ensure they only contain `http`, `https`, `ftp` and `mailto` URLs. Relative URLs are also allowed. Ditto for `src` attributes.
 
@@ -293,14 +293,18 @@ If you wish to expand this list, for instance to discard whatever is found insid
 nonTextTags: [ 'style', 'script', 'textarea', 'noscript' ]
 ```
 
 Note that if you use this option you are responsible for stating the entire list. This gives you the power to retain the content of `textarea`, if you want to.
 
+The content still gets escaped properly, with the exception of the `script` and `style` tags. *Allowing either `script` or `style` leaves you open to XSS attacks. Don't do that* unless you have good reason to trust their origin.
+
 ## Changelog
 
 1.11.4: fixed crash when `__proto__` is a tag name. Now using a safe check for the existence of properties in all cases. Thanks to Andrew Krasichkov.
 
+Fixed XSS attack vector via `textarea` tags (when explicitly allowed). Decided that `script` (obviously) and `style` (due to its own XSS vectors) cannot realistically be afforded any XSS protection if allowed, unless we add a full CSS parser. Thanks again to Andrew Krasichkov.
+
 1.11.3: bumped `htmlparser2` version to address crashing bug in older version. Thanks to e-jigsaw.
 
 1.11.2: fixed README typo that interfered with readability due to markdown issues. No code changes. Thanks to Mikael Korpela. Also improved code block highlighting in README. Thanks to Alex Siman.
 
 1.11.1: fixed a regression introduced in 1.11.0 which caused the closing tag of the parent of a `textarea` tag to be lost. Thanks to Stefano Sala, who contributed the missing test.
diff --git a/index.js b/index.js
index db9f62f..6eecfde 100644
--- a/index.js
+++ b/index.js
@@ -203,11 +203,15 @@ function sanitizeHtml(html, options, _recursing) {
         tag = lastFrame.tag;
         // If inner text was set by transform function then let's use it
         text = lastFrame.innerText !== undefined ? lastFrame.innerText : text;
       }
 
-      if (nonTextTagsArray.indexOf(tag) !== -1) {
+      if ((tag === 'script') || (tag === 'style')) {
+        // htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing
+        // script tags is, by definition, game over for XSS protection, so if that's
+        // your concern, don't allow them. The same is essentially true for style tags
+        // which have their own collection of XSS vectors.
         result += text;
       } else {
         var escaped = escapeHtml(text);
         if (options.textFilter) {
           result += options.textFilter(escaped);
diff --git a/test/test.js b/test/test.js
index 9c39889..c418f3c 100644
--- a/test/test.js
+++ b/test/test.js
@@ -431,11 +431,11 @@ describe('sanitizeHtml', function() {
         allowedTags: [ 'a' ],
         allowedAttributes: { a: ['data-b.*'] }
       }), '<a data-b.c="#test">click me</a>'
     );
   });
-  it('should not escape inner content from non-text tags (when allowed)', function() {
+  it('should not escape inner content of script and style tags (when allowed)', function() {
     assert.equal(
       sanitizeHtml('<div>"normal text"</div><script>"this is code"</script>', {
         allowedTags: [ 'script' ]
       }), '&quot;normal text&quot;<script>"this is code"</script>'
     );
@@ -443,10 +443,20 @@ describe('sanitizeHtml', function() {
       sanitizeHtml('<div>"normal text"</div><style>body { background-image: url("image.test"); }</style>', {
         allowedTags: [ 'style' ]
       }), '&quot;normal text&quot;<style>body { background-image: url("image.test"); }</style>'
     );
   });
+  it('should not unescape escapes found inside script tags', function() {
+    assert.equal(
+      sanitizeHtml('<script>alert("&quot;This is cool but just ironically so I quoted it&quot;")</script>',
+        {
+          allowedTags: [ 'script' ]
+        }
+      ),
+      '<script>alert("&quot;This is cool but just ironically so I quoted it&quot;")</script>'
+    )
+  });
   it('should process text nodes with provided function', function() {
     assert.equal(
       sanitizeHtml('"normal text this should be removed"', {
         textFilter: function(text) {
           return text.replace(' this should be removed', '');
@@ -476,6 +486,13 @@ describe('sanitizeHtml', function() {
   it('should not crash due to tag names that are properties of the universal Object prototype', function() {
     assert.equal(
       sanitizeHtml("!<__proto__>!"),
     "!!");
   });
+  it('should correctly maintain escaping when allowing a nonTextTags tag other than script or style', function() {
+    assert.equal(
+      sanitizeHtml('!<textarea>&lt;/textarea&gt;&lt;svg/onload=prompt`xs`&gt;</textarea>!',
+        { allowedTags: [ 'textarea' ] }
+      ), '!<textarea>&lt;/textarea&gt;&lt;svg/onload=prompt`xs`&gt;</textarea>!'
+    );
+  });
 });

Vulnerable code: If a tag is not permitted, the contents of the tag are still kept, except for script and style tags.
      if (nonTextTagsArray.indexOf(tag) !== -1) {
  it('should not escape inner content from non-text tags (when allowed)', function() {
Fixed code: If a tag is not permitted, the contents of the tag are still kept, except for `script`, `style` and `textarea` tags.
The content still gets escaped properly, with the exception of the `script` and `style` tags. *Allowing either `script` or `style` leaves you open to XSS attacks. Don't do that* unless you have good reason to trust their origin.

Fixed XSS attack vector via `textarea` tags (when explicitly allowed). Decided that `script` (obviously) and `style` (due to its own XSS vectors) cannot realistically be afforded any XSS protection if allowed, unless we add a full CSS parser. Thanks again to Andrew Krasichkov.

      if ((tag === 'script') || (tag === 'style')) {
        // htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing
        // script tags is, by definition, game over for XSS protection, so if that's
        // your concern, don't allow them. The same is essentially true for style tags
        // which have their own collection of XSS vectors.
  it('should not escape inner content of script and style tags (when allowed)', function() {
  it('should not unescape escapes found inside script tags', function() {
    assert.equal(
      sanitizeHtml('<script>alert("&quot;This is cool but just ironically so I quoted it&quot;")</script>',
        {
          allowedTags: [ 'script' ]
        }
      ),
      '<script>alert("&quot;This is cool but just ironically so I quoted it&quot;")</script>'
    )
  });
  it('should correctly maintain escaping when allowing a nonTextTags tag other than script or style', function() {
    assert.equal(
      sanitizeHtml('!<textarea>&lt;/textarea&gt;&lt;svg/onload=prompt`xs`&gt;</textarea>!',
        { allowedTags: [ 'textarea' ] }
      ), '!<textarea>&lt;/textarea&gt;&lt;svg/onload=prompt`xs`&gt;</textarea>!'
    );
  });