Skip to content

Commit

Permalink
Improve Froala sanitization of pasted content.
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke Towers committed Jun 18, 2020
1 parent 0cdda52 commit b384954
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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))
Expand Down Expand Up @@ -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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion modules/system/assets/js/framework-min.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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);
4 changes: 3 additions & 1 deletion modules/system/assets/js/framework.combined-min.js
Original file line number Diff line number Diff line change
Expand Up @@ -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})}})
Expand Down
68 changes: 68 additions & 0 deletions modules/system/assets/js/framework.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

0 comments on commit b384954

Please sign in to comment.