CVE-2021-3649: Other (NVD-CWE-Other)
value: chatwoot is vulnerable to Inefficient Regular Expression Complexity
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 7.5
Published: 2021-07-16T14:15Z
Repository: https://github.com/chatwoot/chatwoot
Language: JavaScript
Files changed: app/javascript/dashboard/components/widgets/conversation/Message.vue, app/javascript/shared/mixins/messageFormatterMixin.js, app/javascript/shared/mixins/specs/messageFormatterMixin.spec.js
Commit date: 2021-07-15 12:54:31 +0530
Commit message: fix: Use Dompurify to strip style characters (#2632)


Diff: diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue
index 1fce9bcc..ae58222f 100644
--- a/app/javascript/dashboard/components/widgets/conversation/Message.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue
@@ -89,11 +89,10 @@ import { isEmptyObject } from 'dashboard/helper/commons';
 import alertMixin from 'shared/mixins/alertMixin';
 import contentTypeMixin from 'shared/mixins/contentTypeMixin';
 import BubbleActions from './bubble/Actions';
 import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
 import { generateBotMessageContent } from './helpers/botMessageContentHelper';
-import { stripStyleCharacters } from './helpers/EmailContentParser';
 
 export default {
   components: {
     BubbleActions,
     BubbleText,
@@ -138,11 +137,11 @@ export default {
         } = {},
       } = this.contentAttributes;
 
       if ((replyHTMLContent || fullHTMLContent) && this.isIncoming) {
         let contentToBeParsed = replyHTMLContent || fullHTMLContent || '';
-        const parsedContent = stripStyleCharacters(contentToBeParsed);
+        const parsedContent = this.stripStyleCharacters(contentToBeParsed);
         if (parsedContent) {
           return parsedContent;
         }
       }
       return (
diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/EmailContentParser.js b/app/javascript/dashboard/components/widgets/conversation/helpers/EmailContentParser.js
deleted file mode 100644
index 1f716fad..00000000
--- a/app/javascript/dashboard/components/widgets/conversation/helpers/EmailContentParser.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export const stripStyleCharacters = emailContent => {
-  let contentToBeParsed = emailContent.replace(/<style(.|\s)*?<\/style>/g, '');
-  contentToBeParsed = contentToBeParsed.replace(/style="(.*?)"/g, '');
-  let parsedContent = new DOMParser().parseFromString(
-    contentToBeParsed,
-    'text/html'
-  );
-  if (!parsedContent.getElementsByTagName('parsererror').length) {
-    return parsedContent.body.innerHTML;
-  }
-  return '';
-};
diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/specs/EmailContentParser.spec.js b/app/javascript/dashboard/components/widgets/conversation/helpers/specs/EmailContentParser.spec.js
deleted file mode 100644
index 663d3173..00000000
--- a/app/javascript/dashboard/components/widgets/conversation/helpers/specs/EmailContentParser.spec.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { stripStyleCharacters } from '../EmailContentParser';
-
-describe('#stripStyleCharacters', () => {
-  it('remove style characters', () => {
-    expect(
-      stripStyleCharacters(
-        `<html><body><style type="text/css"> \n<!-- \nimg \n	{max-width:100%} \ndiv \n	{width:100%!important; \n	height:100%; \n	line-height:1.6em} \ndiv \n	{background-color:#f6f6f6} \n--> \n</style>\n<div itemscope="" itemtype="http://schema.org/EmailMessage" style="font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing:border-box; font-size:14px; width:100%!important; height:100%; line-height:1.6em; background-color:#f6f6f6; margin:0; background-color:#f6f6f6">Test Content</div>\n</body></html>`
-      )
-    ).toEqual(
-      '\n<div itemscope="" itemtype="http://schema.org/EmailMessage">Test Content</div>\n'
-    );
-  });
-});
diff --git a/app/javascript/shared/mixins/messageFormatterMixin.js b/app/javascript/shared/mixins/messageFormatterMixin.js
index 5dacc43f..3d6902bf 100644
--- a/app/javascript/shared/mixins/messageFormatterMixin.js
+++ b/app/javascript/shared/mixins/messageFormatterMixin.js
@@ -1,6 +1,7 @@
 import MessageFormatter from '../helpers/MessageFormatter';
+import DOMPurify from 'dompurify';
 
 export default {
   methods: {
     formatMessage(message, isATweet) {
       const messageFormatter = new MessageFormatter(message, isATweet);
@@ -15,7 +16,26 @@ export default {
         return description;
       }
 
       return `${description.slice(0, 97)}...`;
     },
+    stripStyleCharacters(message) {
+      return DOMPurify.sanitize(message, {
+        FORBID_TAGS: ['style'],
+        FORBID_ATTR: [
+          'id',
+          'class',
+          'style',
+          'bgcolor',
+          'valign',
+          'width',
+          'face',
+          'color',
+          'height',
+          'lang',
+          'align',
+          'size',
+        ],
+      });
+    },
   },
 };
diff --git a/app/javascript/shared/mixins/specs/messageFormatterMixin.spec.js b/app/javascript/shared/mixins/specs/messageFormatterMixin.spec.js
index 81f653d7..b43129ae 100644
--- a/app/javascript/shared/mixins/specs/messageFormatterMixin.spec.js
+++ b/app/javascript/shared/mixins/specs/messageFormatterMixin.spec.js
@@ -12,6 +12,19 @@ describe('messageFormatterMixin', () => {
       '<b>Chatwoot is an opensource tool. https://www.chatwoot.com</b>';
     expect(wrapper.vm.getPlainText(message)).toMatch(
       'Chatwoot is an opensource tool. https://www.chatwoot.com'
     );
   });
+
+  it('stripStyleCharacters returns message without style tags', () => {
+    const Component = {
+      render() {},
+      mixins: [messageFormatterMixin],
+    };
+    const wrapper = shallowMount(Component);
+    const message =
+      '<b style="max-width:100%">Chatwoot is an opensource tool. https://www.chatwoot.com</b><style type="css">.message{}</style>';
+    expect(wrapper.vm.stripStyleCharacters(message)).toMatch(
+      '<b>Chatwoot is an opensource tool. https://www.chatwoot.com</b>'
+    );
+  });
 });

Vulnerable code: import { stripStyleCharacters } from './helpers/EmailContentParser';
        const parsedContent = stripStyleCharacters(contentToBeParsed);
export const stripStyleCharacters = emailContent => {
  let contentToBeParsed = emailContent.replace(/<style(.|\s)*?<\/style>/g, '');
  contentToBeParsed = contentToBeParsed.replace(/style="(.*?)"/g, '');
  let parsedContent = new DOMParser().parseFromString(
    contentToBeParsed,
    'text/html'
  );
  if (!parsedContent.getElementsByTagName('parsererror').length) {
    return parsedContent.body.innerHTML;
  }
  return '';
};
import { stripStyleCharacters } from '../EmailContentParser';

describe('#stripStyleCharacters', () => {
  it('remove style characters', () => {
    expect(
      stripStyleCharacters(
        `<html><body><style type="text/css"> \n<!-- \nimg \n	{max-width:100%} \ndiv \n	{width:100%!important; \n	height:100%; \n	line-height:1.6em} \ndiv \n	{background-color:#f6f6f6} \n--> \n</style>\n<div itemscope="" itemtype="http://schema.org/EmailMessage" style="font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing:border-box; font-size:14px; width:100%!important; height:100%; line-height:1.6em; background-color:#f6f6f6; margin:0; background-color:#f6f6f6">Test Content</div>\n</body></html>`
      )
    ).toEqual(
      '\n<div itemscope="" itemtype="http://schema.org/EmailMessage">Test Content</div>\n'
    );
  });
});
Fixed code:         const parsedContent = this.stripStyleCharacters(contentToBeParsed);
import DOMPurify from 'dompurify';
    stripStyleCharacters(message) {
      return DOMPurify.sanitize(message, {
        FORBID_TAGS: ['style'],
        FORBID_ATTR: [
          'id',
          'class',
          'style',
          'bgcolor',
          'valign',
          'width',
          'face',
          'color',
          'height',
          'lang',
          'align',
          'size',
        ],
      });
    },

  it('stripStyleCharacters returns message without style tags', () => {
    const Component = {
      render() {},
      mixins: [messageFormatterMixin],
    };
    const wrapper = shallowMount(Component);
    const message =
      '<b style="max-width:100%">Chatwoot is an opensource tool. https://www.chatwoot.com</b><style type="css">.message{}</style>';
    expect(wrapper.vm.stripStyleCharacters(message)).toMatch(
      '<b>Chatwoot is an opensource tool. https://www.chatwoot.com</b>'
    );
  });