CVE-2020-4061 (JavaScript, published 2020-07-02T17:15Z)
Description: value: 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.
Severity: LOW (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
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.
Repository: https://github.com/octobercms/october
Files changed:
modules/backend/formwidgets/mediafinder/assets/js/mediafinder.js
modules/backend/formwidgets/richeditor/assets/js/build-plugins-min.js
modules/backend/formwidgets/richeditor/assets/js/richeditor.js
modules/system/assets/js/framework-min.js
modules/system/assets/js/framework.combined-min.js
modules/system/assets/js/framework.js
Commit message: Improve Froala sanitization of pasted content.


Vulnerable code:
 * $('a#someElement').recordFinder({ option: 'value' })
        // In some cases options could contain callbacks, 
window.ocJSON=function(json){var jsonString=parse(json);return JSON.parse(jsonString);};}(window);
window.ocJSON=function(json){var jsonString=parse(json);return JSON.parse(jsonString);};}(window);+function($){"use strict";if($.oc===undefined)

Fixed code:
 * $('a#someElement').mediaFinder({ option: 'value' })
        // In some cases options could contain callbacks,
this.$textarea.on('froalaEditor.paste.beforeCleanup',this.proxy(this.beforeCleanupPaste))
this.$textarea.off('froalaEditor.paste.beforeCleanup',this.proxy(this.beforeCleanupPaste))
RichEditor.prototype.beforeCleanupPaste=function(ev,editor,clipboard_html){return ocSanitize(clipboard_html)}
        this.$textarea.on('froalaEditor.paste.beforeCleanup', this.proxy(this.beforeCleanupPaste))
        this.$textarea.off('froalaEditor.paste.beforeCleanup', this.proxy(this.beforeCleanupPaste))
    RichEditor.prototype.beforeCleanupPaste = function (ev, editor, clipboard_html) {
        return ocSanitize(clipboard_html)
    }

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);
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)

/*
 * 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);