From 7227362633d0013152297e232253edb167989ce8 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] 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
---
 resources/src/mediawiki/mediawiki.jqueryMsg.js     | 21 ++++++++++++--
 .../mediawiki/mediawiki.jqueryMsg.test.js          | 33 ++++++++++++++++++++++
 2 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/resources/src/mediawiki/mediawiki.jqueryMsg.js b/resources/src/mediawiki/mediawiki.jqueryMsg.js
index bf0884b..d412a7b 100644
--- a/resources/src/mediawiki/mediawiki.jqueryMsg.js
+++ b/resources/src/mediawiki/mediawiki.jqueryMsg.js
@@ -663,7 +663,7 @@
 			 * @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();
@@ -671,12 +671,17 @@
 					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 ( $.inArray( attributeName, settings.allowedHtmlCommonAttributes ) === -1 &&
 						$.inArray( attributeName, settings.allowedHtmlAttributesByElement[ startTagName ] || [] ) === -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;
@@ -1079,7 +1084,8 @@
 		extlink: function ( nodes ) {
 			var $el,
 				arg = nodes[ 0 ],
-				contents = nodes[ 1 ];
+				contents = nodes[ 1 ],
+				target;
 			if ( arg instanceof jQuery && !arg.hasClass( 'mediaWiki_htmlEmitter' ) ) {
 				$el = arg;
 			} else {
@@ -1091,7 +1097,16 @@
 					} )
 					.click( arg );
 				} 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 f65ff16..213b113 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
@@ -1078,6 +1078,39 @@
 		assert.equal( logSpy.callCount, 2, 'mw.log.warn calls' );
 	} );
 
+	QUnit.test( 'Do not allow javascript: urls', 2, function ( assert ) {
+		mw.messages.set( 'illegal-url', '[javascript:alert(1) foo]' );
+		mw.messages.set( 'illegal-url-param', '[$1 foo]' );
+
+		this.suppressWarnings();
+
+		assert.equal(
+			formatParse( 'illegal-url' ),
+			'[javascript:alert(1) foo]',
+			'illegal-url: \'parse\' format'
+		);
+
+		assert.equal(
+			/* jshint scripturl: true */
+			formatParse( 'illegal-url-param', 'javascript:alert(1)' ),
+			/* jshint scripturl: false */
+			'[javascript:alert(1) foo]',
+			'illegal-url-param: \'parse\' format'
+		);
+	} );
+
+	QUnit.test( 'Do not allow arbitrary style', 1, function ( assert ) {
+		mw.messages.set( 'illegal-style', '<span style="background-image:url( http://example.com )">bar</span>' );
+
+		this.suppressWarnings();
+
+		assert.equal(
+			formatParse( 'illegal-style' ),
+			'&lt;span style="background-image:url( http://example.com )"&gt;bar&lt;/span&gt;',
+			'illegal-style: \'parse\' format'
+		);
+	} );
+
 	QUnit.test( 'Integration', 5, function ( assert ) {
 		var expected, logSpy, msg;
 
-- 
1.9.5.msysgit.0

