VYPR
Low severityNVD Advisory· Published Jul 2, 2020· Updated Aug 4, 2024

Cross-site Scripting in OctoberPotential self-XSS when pasting content from malicious websites

CVE-2020-4061

Description

In October from version 1.0.319 and before version 1.0.467, pasting content copied from malicious websites into the Froala richeditor could result in a successful self-XSS attack. This has been fixed in 1.0.467.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
october/backendPackagist
>= 1.0.319, < 1.0.4671.0.467

Affected products

1

Patches

1
b384954a29b8

Improve Froala sanitization of pasted content.

https://github.com/octobercms/octoberLuke TowersJun 18, 2020via ghsa
6 files changed · +85 4
  • modules/backend/formwidgets/mediafinder/assets/js/mediafinder.js+2 2 modified
    @@ -6,7 +6,7 @@
      * - data-option="value" - an option with a value
      *
      * JavaScript API:
    - * $('a#someElement').recordFinder({ option: 'value' })
    + * $('a#someElement').mediaFinder({ option: 'value' })
      *
      * Dependences:
      * - Some other plugin (filename.js)
    @@ -71,7 +71,7 @@
             this.$findValue = null
             this.$el = null
     
    -        // In some cases options could contain callbacks, 
    +        // In some cases options could contain callbacks,
             // so it's better to clean them up too.
             this.options = null
     
    
  • modules/backend/formwidgets/richeditor/assets/js/build-plugins-min.js+3 0 modified
    @@ -194,6 +194,7 @@ this.$textarea.on('froalaEditor.initialized',this.proxy(this.build))
     this.$textarea.on('froalaEditor.contentChanged',this.proxy(this.onChange))
     this.$textarea.on('froalaEditor.html.get',this.proxy(this.onSyncContent))
     this.$textarea.on('froalaEditor.html.set',this.proxy(this.onSetContent))
    +this.$textarea.on('froalaEditor.paste.beforeCleanup',this.proxy(this.beforeCleanupPaste))
     this.$form.on('oc.beforeRequest',this.proxy(this.onFormBeforeRequest))
     this.$textarea.froalaEditor(froalaOptions)
     this.editor=this.$textarea.data('froala.editor')
    @@ -213,6 +214,7 @@ this.$textarea.off('froalaEditor.initialized',this.proxy(this.build))
     this.$textarea.off('froalaEditor.contentChanged',this.proxy(this.onChange))
     this.$textarea.off('froalaEditor.html.get',this.proxy(this.onSyncContent))
     this.$textarea.off('froalaEditor.html.set',this.proxy(this.onSetContent))
    +this.$textarea.off('froalaEditor.paste.beforeCleanup',this.proxy(this.beforeCleanupPaste))
     this.$form.off('oc.beforeRequest',this.proxy(this.onFormBeforeRequest))
     $(window).off('resize',this.proxy(this.updateLayout))
     $(window).off('oc.updateUi',this.proxy(this.updateLayout))
    @@ -243,6 +245,7 @@ RichEditor.prototype.insertUiBlock=function($node){this.$textarea.froalaEditor('
     RichEditor.prototype.insertVideo=function(url,title){this.$textarea.froalaEditor('figures.insertVideo',url,title)}
     RichEditor.prototype.insertAudio=function(url,title){this.$textarea.froalaEditor('figures.insertAudio',url,title)}
     RichEditor.prototype.onSetContent=function(ev,editor){this.$textarea.trigger('setContent.oc.richeditor',[this])}
    +RichEditor.prototype.beforeCleanupPaste=function(ev,editor,clipboard_html){return ocSanitize(clipboard_html)}
     RichEditor.prototype.onSyncContent=function(ev,editor,html){if(editor.codeBeautifier){html=editor.codeBeautifier.run(html,editor.opts.codeBeautifierOptions)}
     var container={html:html}
     this.$textarea.trigger('syncContent.oc.richeditor',[this,container])
    
  • modules/backend/formwidgets/richeditor/assets/js/richeditor.js+6 0 modified
    @@ -209,6 +209,7 @@
             this.$textarea.on('froalaEditor.contentChanged', this.proxy(this.onChange))
             this.$textarea.on('froalaEditor.html.get', this.proxy(this.onSyncContent))
             this.$textarea.on('froalaEditor.html.set', this.proxy(this.onSetContent))
    +        this.$textarea.on('froalaEditor.paste.beforeCleanup', this.proxy(this.beforeCleanupPaste))
             this.$form.on('oc.beforeRequest', this.proxy(this.onFormBeforeRequest))
     
             this.$textarea.froalaEditor(froalaOptions)
    @@ -245,6 +246,7 @@
             this.$textarea.off('froalaEditor.contentChanged', this.proxy(this.onChange))
             this.$textarea.off('froalaEditor.html.get', this.proxy(this.onSyncContent))
             this.$textarea.off('froalaEditor.html.set', this.proxy(this.onSetContent))
    +        this.$textarea.off('froalaEditor.paste.beforeCleanup', this.proxy(this.beforeCleanupPaste))
             this.$form.off('oc.beforeRequest', this.proxy(this.onFormBeforeRequest))
     
             $(window).off('resize', this.proxy(this.updateLayout))
    @@ -344,6 +346,10 @@
             this.$textarea.trigger('setContent.oc.richeditor', [this])
         }
     
    +    RichEditor.prototype.beforeCleanupPaste = function (ev, editor, clipboard_html) {
    +        return ocSanitize(clipboard_html)
    +    }
    +
         RichEditor.prototype.onSyncContent = function(ev, editor, html) {
             // Beautify HTML.
             if (editor.codeBeautifier) {
    
  • modules/system/assets/js/framework.combined-min.js+3 1 modified
    @@ -185,7 +185,9 @@ if(str[0]==="["){var result="[";var type="needBody";for(var i=1;i<str.length;i++
     if(str[i]==="]"&&i===str.length-1){if(result[result.length-1]===",")result=result.substr(0,result.length-1);result+="]";return result;}
     var body=getBody(str,i);i=i+body.originLength-1;result+=parse(body.body);type="afterBody";}else if(type==="afterBody"){if(str[i]===","){result+=",";type="needBody";while(str[i+1]===","||isBlankChar(str[i+1])){if(str[i+1]===",")result+="null,";i++;}}else if(str[i]==="]"&&i===str.length-1){result+="]";return result;}}}
     throw new Error("Broken JSON array near "+result);}}
    -window.ocJSON=function(json){var jsonString=parse(json);return JSON.parse(jsonString);};}(window);+function($){"use strict";if($.oc===undefined)
    +window.ocJSON=function(json){var jsonString=parse(json);return JSON.parse(jsonString);};}(window);+function(window){"use strict";function trimAttributes(node){$.each(node.attributes,function(){var attrName=this.name;var attrValue=this.value;if(attrName.indexOf('on')==0||attrValue.indexOf('javascript:')==0){$(node).removeAttr(attrName);}});}
    +function sanitize(html){var output=$($.parseHTML('<div>'+html+'</div>',null,false));output.find('*').each(function(){trimAttributes(this);});return output.html();}
    +window.ocSanitize=function(html){return sanitize(html)};}(window);+function($){"use strict";if($.oc===undefined)
     $.oc={}
     var LOADER_CLASS='oc-loading';$(document).on('ajaxSetup','[data-request][data-request-flash]',function(event,context){context.options.handleErrorMessage=function(message){$.oc.flashMsg({text:message,class:'error'})}
     context.options.handleFlashMessage=function(message,type){$.oc.flashMsg({text:message,class:type})}})
    
  • modules/system/assets/js/framework.js+68 0 modified
    @@ -907,3 +907,71 @@ if (window.jQuery.request !== undefined) {
         };
     
     }(window);
    +
    +/*
    + * October CMS jQuery HTML Sanitizer
    + * @see https://gist.github.com/ufologist/5a0da51b2b9ef1b861c30254172ac3c9
    + */
    ++function(window) { "use strict";
    +
    +    function trimAttributes(node) {
    +        $.each(node.attributes, function() {
    +            var attrName = this.name;
    +            var attrValue = this.value;
    +
    +            /*
    +             * remove attributes where the names start with "on" (for example: onload, onerror...)
    +             * remove attributes where the value starts with the "javascript:" pseudo protocol (for example href="javascript:alert(1)")
    +             */
    +            if (attrName.indexOf('on') == 0 || attrValue.indexOf('javascript:') == 0) {
    +                $(node).removeAttr(attrName);
    +            }
    +        });
    +    }
    +
    +    function sanitize(html) {
    +        /*
    +         * [jQuery.parseHTML(data [, context ] [, keepScripts ])](http://api.jquery.com/jQuery.parseHTML/) added: 1.8
    +         * Parses a string into an array of DOM nodes.
    +         *
    +         * By default, the context is the current document if not specified or given as null or undefined. If the HTML was to be used
    +         * in another document such as an iframe, that frame's document could be used.
    +         *
    +         * As of 3.0 the default behavior is changed.
    +         *
    +         * If the context is not specified or given as null or undefined, a new document is used.
    +         * This can potentially improve security because inline events will not execute when the HTML is parsed. Once the parsed HTML
    +         * is injected into a document it does execute, but this gives tools a chance to traverse the created DOM and remove anything
    +         * deemed unsafe. This improvement does not apply to internal uses of jQuery.parseHTML as they usually pass in the current
    +         * document. Therefore, a statement like $( "#log" ).append( $( htmlString ) ) is still subject to the injection of malicious code.
    +         *
    +         * without context do not execute script
    +         * $.parseHTML('<div><img src=1 onerror=alert(1)></div>');
    +         * $.parseHTML('<div><img src=1 onerror=alert(2)></div>', null);
    +         *
    +         * with context document execute script!
    +         * $.parseHTML('<div><img src=1 onerror=alert(3)></div>', document);
    +         *
    +         * Most jQuery APIs that accept HTML strings will run scripts that are included in the HTML. jQuery.parseHTML does not run scripts
    +         * in the parsed HTML unless keepScripts is explicitly true. However, it is still possible in most environments to execute scripts
    +         * indirectly, for example via the <img onerror> attribute.
    +         *
    +         * will return []
    +         * $.parseHTML('<script>alert(1)<\/script>', null, false);
    +         *
    +         * will return [script DOM element]
    +         * $.parseHTML('<script>alert(1)<\/script>', null, true);
    +         */
    +        var output = $($.parseHTML('<div>' + html + '</div>', null, false));
    +        output.find('*').each(function() {
    +            trimAttributes(this);
    +        });
    +        return output.html();
    +    }
    +
    +    // Global function
    +    window.ocSanitize = function(html) {
    +        return sanitize(html)
    +    };
    +
    +}(window);
    \ No newline at end of file
    
  • modules/system/assets/js/framework-min.js+3 1 modified
    @@ -185,4 +185,6 @@ if(str[0]==="["){var result="[";var type="needBody";for(var i=1;i<str.length;i++
     if(str[i]==="]"&&i===str.length-1){if(result[result.length-1]===",")result=result.substr(0,result.length-1);result+="]";return result;}
     var body=getBody(str,i);i=i+body.originLength-1;result+=parse(body.body);type="afterBody";}else if(type==="afterBody"){if(str[i]===","){result+=",";type="needBody";while(str[i+1]===","||isBlankChar(str[i+1])){if(str[i+1]===",")result+="null,";i++;}}else if(str[i]==="]"&&i===str.length-1){result+="]";return result;}}}
     throw new Error("Broken JSON array near "+result);}}
    -window.ocJSON=function(json){var jsonString=parse(json);return JSON.parse(jsonString);};}(window);
    \ No newline at end of file
    +window.ocJSON=function(json){var jsonString=parse(json);return JSON.parse(jsonString);};}(window);+function(window){"use strict";function trimAttributes(node){$.each(node.attributes,function(){var attrName=this.name;var attrValue=this.value;if(attrName.indexOf('on')==0||attrValue.indexOf('javascript:')==0){$(node).removeAttr(attrName);}});}
    +function sanitize(html){var output=$($.parseHTML('<div>'+html+'</div>',null,false));output.find('*').each(function(){trimAttributes(this);});return output.html();}
    +window.ocSanitize=function(html){return sanitize(html)};}(window);
    \ No newline at end of file
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.