diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index c844abe75e49..41e8f633c99b 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -395,6 +395,27 @@ public bool UseHttps
}
}
+ ///
+ /// Returns true if TinyMCE scripting sanitization should be applied
+ ///
+ ///
+ /// The default value is false
+ ///
+ public bool SanitizeTinyMce
+ {
+ get
+ {
+ try
+ {
+ return bool.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.SanitizeTinyMce]);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+
///
/// An int value representing the time in milliseconds to lock the database for a write operation
///
diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs
index 483829f85ff3..2ebab722f001 100644
--- a/src/Umbraco.Core/Configuration/IGlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs
@@ -77,5 +77,10 @@ public interface IGlobalSettings
/// Gets the write lock timeout.
///
int SqlWriteLockTimeOut { get; }
+
+ ///
+ /// Returns true if TinyMCE scripting sanitization should be applied
+ ///
+ bool SanitizeTinyMce { get; }
}
}
diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs
index 99ea26b4d698..de7799c1655c 100644
--- a/src/Umbraco.Core/Constants-AppSettings.cs
+++ b/src/Umbraco.Core/Constants-AppSettings.cs
@@ -109,7 +109,7 @@ public static class AppSettings
/// A true or false indicating whether umbraco should force a secure (https) connection to the backoffice.
///
public const string UseHttps = "Umbraco.Core.UseHttps";
-
+
///
/// A true/false value indicating whether the content dashboard should be visible for all user groups.
///
@@ -155,6 +155,11 @@ public static class Debug
/// An int value representing the time in milliseconds to lock the database for a write operation
///
public const string SqlWriteLockTimeOut = "Umbraco.Core.SqlWriteLockTimeOut";
+
+ ///
+ /// Returns true if TinyMCE scripting sanitization should be applied
+ ///
+ public const string SanitizeTinyMce = "Umbraco.Web.SanitizeTinyMce";
}
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
index 46c047448f88..fe3219e2b7aa 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
@@ -1491,6 +1491,19 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
});
}
+
+ if(Umbraco.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce === true){
+ /** prevent injecting arbitrary JavaScript execution in on-attributes. */
+ const allNodes = Array.prototype.slice.call(args.editor.dom.doc.getElementsByTagName("*"));
+ allNodes.forEach(node => {
+ for (var i = 0; i < node.attributes.length; i++) {
+ if(node.attributes[i].name.indexOf("on") === 0) {
+ node.removeAttribute(node.attributes[i].name)
+ }
+ }
+ });
+ }
+
});
args.editor.on('init', function (e) {
@@ -1502,6 +1515,60 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
//enable browser based spell checking
args.editor.getBody().setAttribute('spellcheck', true);
+
+ /** Setup sanitization for preventing injecting arbitrary JavaScript execution in attributes:
+ * https://github.com/advisories/GHSA-w7jx-j77m-wp65
+ * https://github.com/advisories/GHSA-5vm8-hhgr-jcjp
+ */
+ const uriAttributesToSanitize = ['src', 'href', 'data', 'background', 'action', 'formaction', 'poster', 'xlink:href'];
+ const parseUri = function() {
+ // Encapsulated JS logic.
+ const safeSvgDataUrlElements = [ 'img', 'video' ];
+ const scriptUriRegExp = /((java|vb)script|mhtml):/i;
+ const trimRegExp = /[\s\u0000-\u001F]+/g;
+ const isInvalidUri = (uri, tagName) => {
+ if (/^data:image\//i.test(uri)) {
+ return safeSvgDataUrlElements.indexOf(tagName) !== -1 && /^data:image\/svg\+xml/i.test(uri);
+ } else {
+ return /^data:/i.test(uri);
+ }
+ };
+
+ return function parseUri(uri, tagName) {
+ uri = uri.replace(trimRegExp, '');
+ try {
+ // Might throw malformed URI sequence
+ uri = decodeURIComponent(uri);
+ } catch (ex) {
+ // Fallback to non UTF-8 decoder
+ uri = unescape(uri);
+ }
+
+ if (scriptUriRegExp.test(uri)) {
+ return;
+ }
+
+ if (isInvalidUri(uri, tagName)) {
+ return;
+ }
+
+ return uri;
+ }
+ }();
+
+ if(Umbraco.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce === true){
+ args.editor.serializer.addAttributeFilter(uriAttributesToSanitize, function (nodes) {
+ nodes.forEach(function(node) {
+ node.attributes.forEach(function(attr) {
+ const attrName = attr.name.toLowerCase();
+ if(uriAttributesToSanitize.indexOf(attrName) !== -1) {
+ attr.value = parseUri(attr.value, node.name);
+ }
+ });
+ });
+ });
+ }
+
//start watching the value
startWatch();
});
diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config
index e61c6585ad43..f19ab5d3b638 100644
--- a/src/Umbraco.Web.UI/web.Template.config
+++ b/src/Umbraco.Web.UI/web.Template.config
@@ -51,6 +51,7 @@
+