CVE-2021-41184: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: jQuery-UI is the official jQuery user interface library. Prior to version 1.13.0, accepting the value of the `of` option of the `.position()` util from untrusted sources may execute untrusted code. The issue is fixed in jQuery UI 1.13.0. Any string value passed to the `of` option is now treated as a CSS selector. A workaround is to not accept the value of the `of` option from untrusted sources.
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: 6.1
Published: 2021-10-26T15:15Z
Repository: https://github.com/jquery/jquery-ui
Language: JavaScript
Files changed: tests/unit/position/core.js, ui/position.js
Commit date: 2021-05-04 13:31:39 +0200
Commit message: Position: Make sure `of` is treated as a CSS selector

Closes gh-1955
Diff: diff --git a/tests/unit/position/core.js b/tests/unit/position/core.js
index 49f02afab..f5c784242 100644
--- a/tests/unit/position/core.js
+++ b/tests/unit/position/core.js
@@ -111,11 +111,13 @@ QUnit.test( "positions", function( assert ) {
 		} );
 	} );
 } );
 
 QUnit.test( "of", function( assert ) {
-	assert.expect( 9 + ( scrollTopSupport() ? 1 : 0 ) );
+	assert.expect( 10 + ( scrollTopSupport() ? 1 : 0 ) );
+
+	var done = assert.async();
 
 	var event;
 
 	$( "#elx" ).position( {
 		my: "left top",
@@ -223,10 +225,25 @@ QUnit.test( "of", function( assert ) {
 	} );
 	assert.deepEqual( $( "#elx" ).offset(), {
 		top: 600,
 		left: 400
 	}, "event - left top, right bottom" );
+
+	try {
+		$( "#elx" ).position( {
+			my: "left top",
+			at: "right bottom",
+			of: "<img onerror='window.globalOf=true' src='/404' />",
+			collision: "none"
+		} );
+	} catch ( e ) {}
+
+	setTimeout( function() {
+		assert.equal( window.globalOf, undefined, "of treated as a selector" );
+		delete window.globalOf;
+		done();
+	}, 500 );
 } );
 
 QUnit.test( "offsets", function( assert ) {
 	assert.expect( 9 );
 
@@ -332,10 +349,11 @@ QUnit.test( "using", function( assert ) {
 		using: function( position, feedback ) {
 			assert.deepEqual( this, elems[ count ], "correct context for call #" + count );
 			assert.deepEqual( position, expectedPosition, "correct position for call #" + count );
 			assert.deepEqual( feedback.element.element[ 0 ], elems[ count ] );
 			delete feedback.element.element;
+			delete feedback.target.element.prevObject;
 			assert.deepEqual( feedback, expectedFeedback );
 			count++;
 		}
 	} );
 
diff --git a/ui/position.js b/ui/position.js
index 2d1969599..2bd814d93 100644
--- a/ui/position.js
+++ b/ui/position.js
@@ -146,11 +146,16 @@ $.fn.position = function( options ) {
 
 	// Make a copy, we don't want to modify arguments
 	options = $.extend( {}, options );
 
 	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
-		target = $( options.of ),
+
+		// Make sure string options are treated as CSS selectors
+		target = typeof options.of === "string" ?
+			$( document ).find( options.of ) :
+			$( options.of ),
+
 		within = $.position.getWithinInfo( options.within ),
 		scrollInfo = $.position.getScrollInfo( within ),
 		collision = ( options.collision || "flip" ).split( " " ),
 		offsets = {};
 

Vulnerable code: 	assert.expect( 9 + ( scrollTopSupport() ? 1 : 0 ) );
		target = $( options.of ),
Fixed code: 	assert.expect( 10 + ( scrollTopSupport() ? 1 : 0 ) );

	var done = assert.async();

	try {
		$( "#elx" ).position( {
			my: "left top",
			at: "right bottom",
			of: "<img onerror='window.globalOf=true' src='/404' />",
			collision: "none"
		} );
	} catch ( e ) {}

	setTimeout( function() {
		assert.equal( window.globalOf, undefined, "of treated as a selector" );
		delete window.globalOf;
		done();
	}, 500 );
			delete feedback.target.element.prevObject;

		// Make sure string options are treated as CSS selectors
		target = typeof options.of === "string" ?
			$( document ).find( options.of ) :
			$( options.of ),