CVE-2020-4051: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In Dijit before versions 1.11.11, and greater than or equal to 1.12.0 and less than 1.12.9, and greater than or equal to 1.13.0 and less than 1.13.8, and greater than or equal to 1.14.0 and less than 1.14.7, and greater than or equal to 1.15.0 and less than 1.15.4, and greater than or equal to 1.16.0 and less than 1.16.3, there is a cross-site scripting vulnerability in the Editor's LinkDialog plugin. This has been fixed in 1.11.11, 1.12.9, 1.13.8, 1.14.7, 1.15.4, 1.16.3.
CWE description: 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.
CVSS v3 base score: 5.4
Published: 2020-06-15T22:15Z
Repository: https://github.com/dojo/dijit
Language: JavaScript
Files changed: _editor/plugins/LinkDialog.js, tests/editor/test_LinkDialog.html
Commit date: 2020-06-13 05:04:34 -0700
Commit message: Merge pull request from GHSA-cxjc-r2fp-7mq6

* Add config option `allowUnsafeHtml`: default is `false` which results in
  `<` being replaced with `&lt;`
* Add config option `linkFilter`: can be a function or array of filter pairs
  to control exactly what filtering is applied

This update should minimally affect production applications:

* The behavior of existing links with HTML content will be unchanged
* Existing links that are edited and saved will be filtered (this is only if
  the link is edited, other content within the editor can be edited without
  affecting the link)
* Newly created links will be filtered by default
* For production code to continue working as-is with new data the application
  code will have to be updated to specify `true` for the `LinkDialog` plugin's
  `allowUnsafeHtml` option
(cherry picked from commit 7d9d4927a26a246719e153c1fad557b9a956eb60)

Diff: diff --git a/_editor/plugins/LinkDialog.js b/_editor/plugins/LinkDialog.js
index c3f7e016..9068c2d6 100644
--- a/_editor/plugins/LinkDialog.js
+++ b/_editor/plugins/LinkDialog.js
@@ -1,7 +1,8 @@
 define([
 	"require",
+	"dojo/_base/array",
 	"dojo/_base/declare", // declare
 	"dojo/dom-attr", // domAttr.get
 	"dojo/keys", // keys.ENTER
 	"dojo/_base/lang", // lang.delegate lang.hitch lang.trim
 	"dojo/on",
@@ -9,11 +10,11 @@ define([
 	"dojo/query", // query
 	"dojo/string", // string.substitute
 	"../_Plugin",
 	"../../form/DropDownButton",
 	"../range"
-], function(require, declare, domAttr, keys, lang, on, has, query, string,
+], function(require, array, declare, domAttr, keys, lang, on, has, query, string,
 	_Plugin, DropDownButton, rangeapi){
 
 	// module:
 	//		dijit/_editor/plugins/LinkDialog
 
@@ -24,10 +25,25 @@ define([
 		// description:
 		//		The command provided by this plugin is:
 		//
 		//		- createLink
 
+		// allowUnsafeHtml: boolean
+		//		If false (default), the link description will be filtered to prevent HTML content.
+		//		If true no filtering is done, allowing for HTML content within the link element.
+		//		The filter can be specified with the 'linkFilter' option.
+		allowUnsafeHtml: false,
+
+		// linkFilter: function or array of replacement pairs
+		//		If 'allowUnsafeHtml' is false then this filter will be applied to the link Description value.
+		//		function: the function will be invoked with the string value of the Description field and its
+		//			return value will be used
+		//		array: each array item should be an array of two values to pass to String#replace
+		linkFilter: [
+			[/</g, "&lt;"]
+		],
+
 		// Override _Plugin.buttonClass.   This plugin is controlled by a DropDownButton
 		// (which triggers a TooltipDialog).
 		buttonClass: DropDownButton,
 
 		// Override _Plugin.useDefaultCommand... processing is handled by this plugin, not by dijit/Editor.
@@ -250,10 +266,20 @@ define([
 			// tags:
 			//		protected
 			if(args && args.urlInput){
 				args.urlInput = args.urlInput.replace(/"/g, "&quot;");
 			}
+			if(!this.allowUnsafeHtml && args && args.textInput){
+				if(typeof this.linkFilter === 'function'){
+					args.textInput = this.linkFilter(args.textInput);
+				}
+				else{
+					array.forEach(this.linkFilter, function (currentFilter) {
+						args.textInput = args.textInput.replace(currentFilter[0], currentFilter[1]);
+					});
+				}
+			}
 			return args;
 		},
 
 		_createlinkEnabledImpl: function() {
 			// summary:
@@ -627,12 +653,19 @@ define([
 			}
 		}
 	});
 
 	// Register these plugins
-	_Plugin.registry["createLink"] = function(){
-		return new LinkDialog({command: "createLink"});
+	_Plugin.registry["createLink"] = function(args){
+		var pluginOptions = {
+			command: "createLink",
+			allowUnsafeHtml: ("allowUnsafeHtml" in args) ? args.allowUnsafeHtml : false
+		};
+		if("linkFilter" in args){
+			pluginOptions.linkFilter = args.linkFilter;
+		}
+		return new LinkDialog(pluginOptions);
 	};
 	_Plugin.registry["insertImage"] = function(){
 		return new ImgLinkDialog({command: "insertImage"});
 	};
 
diff --git a/tests/editor/test_LinkDialog.html b/tests/editor/test_LinkDialog.html
index 1681b6e9..3d692a05 100644
--- a/tests/editor/test_LinkDialog.html
+++ b/tests/editor/test_LinkDialog.html
@@ -5,10 +5,14 @@
 	<title>Editor Test: LinkDialog Plugin</title>
 
 	<script type="text/javascript" src="../boilerplate.js"></script>
 
 	<script type="text/javascript">
+		function filterLink () {
+			return 'Filtered Value';
+		}
+
 		require([
 			"dojo/parser",
 			"dijit/Editor",
 			"dijit/_editor/plugins/LinkDialog",
 			"dijit/_editor/plugins/ViewSource",
@@ -34,10 +38,26 @@
 			<br>
 			<br>
 		</div>
 	</div>
 
+	<p>Editor with <code>allowUnsafeHtml</code> set to <code>true</code></p>
+	<div style="border: 1px dotted black;">
+		<div id="editorUnsafe" data-dojo-type="dijit/Editor" data-dojo-props='"aria-label":"editor",extraPlugins:[{name: "createLink", allowUnsafeHtml: true}, "insertImage", "viewSource"]'>
+			<p>This editor will allow unrestricted HTML in the Description field of links</p>
+			<br>
+		</div>
+	</div>
+
+	<p>Editor with custom <code>linkFilter</code> function</p>
+	<div style="border: 1px dotted black;">
+		<div id="editorLinkFilter" data-dojo-type="dijit/Editor" data-dojo-props='"aria-label":"editor",extraPlugins:[{name: "createLink", linkFilter: filterLink}, "insertImage", "viewSource"]'>
+			<p>Links created in this editor will always have a description of "Filtered Value", which is the value returned by the custom <code>linkFilter</code> function.</p>
+			<br>
+		</div>
+	</div>
+
 	<p>RTL Editor:</p>
 	<div style="border: 1px dotted black;">
 		<div id="reditor" data-dojo-type="dijit/Editor" dir="rtl" data-dojo-props='"aria-label":"reditor",extraPlugins:["createLink", "insertImage"]'>
 			<ol>
 				<li>The LinkDialog plugin is an 'example' style plugin that shows how to insert basic web links as well as

Vulnerable code: ], function(require, declare, domAttr, keys, lang, on, has, query, string,
	_Plugin.registry["createLink"] = function(){
		return new LinkDialog({command: "createLink"});
Fixed code: 	"dojo/_base/array",
], function(require, array, declare, domAttr, keys, lang, on, has, query, string,
		// allowUnsafeHtml: boolean
		//		If false (default), the link description will be filtered to prevent HTML content.
		//		If true no filtering is done, allowing for HTML content within the link element.
		//		The filter can be specified with the 'linkFilter' option.
		allowUnsafeHtml: false,

		// linkFilter: function or array of replacement pairs
		//		If 'allowUnsafeHtml' is false then this filter will be applied to the link Description value.
		//		function: the function will be invoked with the string value of the Description field and its
		//			return value will be used
		//		array: each array item should be an array of two values to pass to String#replace
		linkFilter: [
			[/</g, "&lt;"]
		],

			if(!this.allowUnsafeHtml && args && args.textInput){
				if(typeof this.linkFilter === 'function'){
					args.textInput = this.linkFilter(args.textInput);
				}
				else{
					array.forEach(this.linkFilter, function (currentFilter) {
						args.textInput = args.textInput.replace(currentFilter[0], currentFilter[1]);
					});
				}
			}
	_Plugin.registry["createLink"] = function(args){
		var pluginOptions = {
			command: "createLink",
			allowUnsafeHtml: ("allowUnsafeHtml" in args) ? args.allowUnsafeHtml : false
		};
		if("linkFilter" in args){
			pluginOptions.linkFilter = args.linkFilter;
		}
		return new LinkDialog(pluginOptions);
		function filterLink () {
			return 'Filtered Value';
		}

	<p>Editor with <code>allowUnsafeHtml</code> set to <code>true</code></p>
	<div style="border: 1px dotted black;">
		<div id="editorUnsafe" data-dojo-type="dijit/Editor" data-dojo-props='"aria-label":"editor",extraPlugins:[{name: "createLink", allowUnsafeHtml: true}, "insertImage", "viewSource"]'>
			<p>This editor will allow unrestricted HTML in the Description field of links</p>
			<br>
		</div>
	</div>

	<p>Editor with custom <code>linkFilter</code> function</p>
	<div style="border: 1px dotted black;">
		<div id="editorLinkFilter" data-dojo-type="dijit/Editor" data-dojo-props='"aria-label":"editor",extraPlugins:[{name: "createLink", linkFilter: filterLink}, "insertImage", "viewSource"]'>
			<p>Links created in this editor will always have a description of "Filtered Value", which is the value returned by the custom <code>linkFilter</code> function.</p>
			<br>
		</div>
	</div>