From 686e17353b936735cb251dd0bedb29dab861520a Mon Sep 17 00:00:00 2001
From: Brian Wolff <bawolff+wn@gmail.com>
Date: Tue, 27 Oct 2015 01:39:34 -0600
Subject: [PATCH] SECURITY: mediawiki.jqueryMsg: Sanitize URLs and 'style'
 attribute

Previously you could leverage the style attribute, and external
links to execute javascript.

Bug: T86738
Change-Id: I6f15ece1db136369e06dfeee34d1a0c5bc03e32b
---
 .../mediawiki.jqueryMsg.js                    | 22 +++++++++++--
 .../mediawiki/mediawiki.jqueryMsg.test.js     | 32 +++++++++++++++++++
 2 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/resources/src/mediawiki.jqueryMsg/mediawiki.jqueryMsg.js b/resources/src/mediawiki.jqueryMsg/mediawiki.jqueryMsg.js
index 989efcec67..0d51e83c08 100644
--- a/resources/src/mediawiki.jqueryMsg/mediawiki.jqueryMsg.js
+++ b/resources/src/mediawiki.jqueryMsg/mediawiki.jqueryMsg.js
@@ -709,7 +709,7 @@ mw.jqueryMsg.Parser.prototype = {
 		 * @return {boolean} true if this is HTML is allowed, false otherwise
 		 */
 		function isAllowedHtml( startTagName, endTagName, attributes ) {
-			var i, len, attributeName;
+			var i, len, attributeName, badStyle;
 
 			startTagName = startTagName.toLowerCase();
 			endTagName = endTagName.toLowerCase();
@@ -717,12 +717,18 @@ mw.jqueryMsg.Parser.prototype = {
 				return false;
 			}
 
+			badStyle = /[\000-\010\013\016-\037\177]|expression|filter\s*:|accelerator\s*:|-o-link\s*:|-o-link-source\s*:|-o-replace\s*:|url\s*\(|image\s*\(|image-set\s*\(/i;
+
 			for ( i = 0, len = attributes.length; i < len; i += 2 ) {
 				attributeName = attributes[ i ];
 				if ( settings.allowedHtmlCommonAttributes.indexOf( attributeName ) === -1 &&
 					( settings.allowedHtmlAttributesByElement[ startTagName ] || [] ).indexOf( attributeName ) === -1 ) {
 					return false;
 				}
+				if ( attributeName === 'style' && attributes[ i + 1 ].search( badStyle ) !== -1 ) {
+					mw.log( 'HTML tag not parsed due to dangerous style attribute' );
+					return false;
+				}
 			}
 
 			return true;
@@ -1230,7 +1236,8 @@ mw.jqueryMsg.HtmlEmitter.prototype = {
 	extlink: function ( nodes ) {
 		var $el,
 			arg = nodes[ 0 ],
-			contents = nodes[ 1 ];
+			contents = nodes[ 1 ],
+			target;
 		if ( arg instanceof $ && !arg.hasClass( 'mediaWiki_htmlEmitter' ) ) {
 			$el = arg;
 		} else {
@@ -1248,7 +1255,16 @@ mw.jqueryMsg.HtmlEmitter.prototype = {
 					}
 				} );
 			} else {
-				$el.attr( 'href', textify( arg ) );
+				target = textify( arg );
+				if ( target.search( new RegExp( '^(/|' + mw.config.get( 'wgUrlProtocols' ) + ')' ) ) !== -1 ) {
+					$el.attr( 'href', target );
+				} else {
+					mw.log( 'External link in message had illegal target ' + target );
+					return appendWithoutParsing(
+						$( '<span>' ),
+						[ '[' + target + ' ' ].concat( contents ).concat( ']' )
+					).contents();
+				}
 			}
 		}
 		return appendWithoutParsing( $el.empty(), contents );
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
index e6b933d35b..bc49b67dae 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
@@ -1173,6 +1173,38 @@
 		}
 	} );
 
+	QUnit.test( 'Do not allow javascript: urls', function ( assert ) {
+		mw.messages.set( 'illegal-url', '[javascript:alert(1) foo]' );
+		mw.messages.set( 'illegal-url-param', '[$1 foo]' );
+
+		this.suppressWarnings();
+
+		assert.strictEqual(
+			formatParse( 'illegal-url' ),
+			'[javascript:alert(1) foo]',
+			'illegal-url: \'parse\' format'
+		);
+
+		assert.strictEqual(
+			// eslint-disable-next-line no-script-url
+			formatParse( 'illegal-url-param', 'javascript:alert(1)' ),
+			'[javascript:alert(1) foo]',
+			'illegal-url-param: \'parse\' format'
+		);
+	} );
+
+	QUnit.test( 'Do not allow arbitrary style', function ( assert ) {
+		mw.messages.set( 'illegal-style', '<span style="background-image:url( http://example.com )">bar</span>' );
+
+		this.suppressWarnings();
+
+		assert.strictEqual(
+			formatParse( 'illegal-style' ),
+			'&lt;span style="background-image:url( http://example.com )"&gt;bar&lt;/span&gt;',
+			'illegal-style: \'parse\' format'
+		);
+	} );
+
 	QUnit.test( 'Integration', function ( assert ) {
 		var expected, msg, $bar;
 
-- 
2.27.0

