CVE-2022-0542: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - DOM in GitHub repository chatwoot/chatwoot prior to 2.7.0.
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: 2022-08-19T18:15Z
Repository: https://github.com/chatwoot/chatwoot
Language: JavaScript
Files changed: app/javascript/shared/helpers/MessageFormatter.js, app/javascript/shared/helpers/specs/MessageFormatter.spec.js
Commit date: 2022-03-14 18:13:21 +0530
Commit message: fix: Handle unsanitized markup in DOM (#4110)


Diff: diff --git a/app/javascript/shared/helpers/MessageFormatter.js b/app/javascript/shared/helpers/MessageFormatter.js
index f57bbd1d5..3bc89ec2f 100644
--- a/app/javascript/shared/helpers/MessageFormatter.js
+++ b/app/javascript/shared/helpers/MessageFormatter.js
@@ -45,11 +45,16 @@ class MessageFormatter {
         TWITTER_HASH_REPLACEMENT
       );
       const markedDownOutput = marked(withHash);
       return markedDownOutput;
     }
-    return marked(this.message, { breaks: true, gfm: true });
+    DOMPurify.addHook('afterSanitizeAttributes', node => {
+      if ('target' in node) node.setAttribute('target', '_blank');
+    });
+    return DOMPurify.sanitize(
+      marked(this.message, { breaks: true, gfm: true })
+    );
   }
 
   get formattedMessage() {
     return this.formatMessage();
   }
diff --git a/app/javascript/shared/helpers/specs/MessageFormatter.spec.js b/app/javascript/shared/helpers/specs/MessageFormatter.spec.js
index 3ac99dbdd..4e13fe832 100644
--- a/app/javascript/shared/helpers/specs/MessageFormatter.spec.js
+++ b/app/javascript/shared/helpers/specs/MessageFormatter.spec.js
@@ -4,18 +4,18 @@ describe('#MessageFormatter', () => {
   describe('content with links', () => {
     it('should format correctly', () => {
       const message =
         'Chatwoot is an opensource tool. [Chatwoot](https://www.chatwoot.com)';
       expect(new MessageFormatter(message).formattedMessage).toMatch(
-        '<p>Chatwoot is an opensource tool. <a rel="noreferrer noopener nofollow" href="https://www.chatwoot.com" class="link" title="" target="_blank">Chatwoot</a></p>'
+        '<p>Chatwoot is an opensource tool. <a title="" class="link" href="https://www.chatwoot.com" rel="noreferrer noopener nofollow" target="_blank">Chatwoot</a></p>'
       );
     });
     it('should format correctly', () => {
       const message =
         'Chatwoot is an opensource tool. https://www.chatwoot.com';
       expect(new MessageFormatter(message).formattedMessage).toMatch(
-        '<p>Chatwoot is an opensource tool. <a rel="noreferrer noopener nofollow" href="https://www.chatwoot.com" class="link" title="" target="_blank">https://www.chatwoot.com</a></p>'
+        '<p>Chatwoot is an opensource tool. <a title="" class="link" href="https://www.chatwoot.com" rel="noreferrer noopener nofollow" target="_blank">https://www.chatwoot.com</a></p>'
       );
     });
   });
 
   describe('parses heading to strong', () => {
@@ -56,6 +56,16 @@ describe('#MessageFormatter', () => {
       expect(new MessageFormatter(message).plainText).toMatch(
         'Chatwoot is an opensource tool. https://www.chatwoot.com'
       );
     });
   });
+
+  describe('#sanitize', () => {
+    it('sanitizes markup and removes all unnecessary elements', () => {
+      const message =
+        '[xssLink](javascript:alert(document.cookie))\n[normalLink](https://google.com)**I am a bold text paragraph**';
+      expect(new MessageFormatter(message).formattedMessage).toMatch(
+        '<p><a title="" class="link" rel="noreferrer noopener nofollow" target="_blank">xssLink</a><br><a title="" class="link" href="https://google.com" rel="noreferrer noopener nofollow" target="_blank">normalLink</a><strong>I am a bold text paragraph</strong></p>'
+      );
+    });
+  });
 });

Vulnerable code:     return marked(this.message, { breaks: true, gfm: true });
        '<p>Chatwoot is an opensource tool. <a rel="noreferrer noopener nofollow" href="https://www.chatwoot.com" class="link" title="" target="_blank">Chatwoot</a></p>'
        '<p>Chatwoot is an opensource tool. <a rel="noreferrer noopener nofollow" href="https://www.chatwoot.com" class="link" title="" target="_blank">https://www.chatwoot.com</a></p>'
Fixed code:     DOMPurify.addHook('afterSanitizeAttributes', node => {
      if ('target' in node) node.setAttribute('target', '_blank');
    });
    return DOMPurify.sanitize(
      marked(this.message, { breaks: true, gfm: true })
    );
        '<p>Chatwoot is an opensource tool. <a title="" class="link" href="https://www.chatwoot.com" rel="noreferrer noopener nofollow" target="_blank">Chatwoot</a></p>'
        '<p>Chatwoot is an opensource tool. <a title="" class="link" href="https://www.chatwoot.com" rel="noreferrer noopener nofollow" target="_blank">https://www.chatwoot.com</a></p>'

  describe('#sanitize', () => {
    it('sanitizes markup and removes all unnecessary elements', () => {
      const message =
        '[xssLink](javascript:alert(document.cookie))\n[normalLink](https://google.com)**I am a bold text paragraph**';
      expect(new MessageFormatter(message).formattedMessage).toMatch(
        '<p><a title="" class="link" rel="noreferrer noopener nofollow" target="_blank">xssLink</a><br><a title="" class="link" href="https://google.com" rel="noreferrer noopener nofollow" target="_blank">normalLink</a><strong>I am a bold text paragraph</strong></p>'
      );
    });
  });