Last modified: 2014-08-16 10:02:02 UTC

Wikimedia Bugzilla is closed!

Wikimedia migrated from Bugzilla to Phabricator. Bug reports are handled in Wikimedia Phabricator.
This static website is read-only and for historical purposes. It is not possible to log in and except for displaying bug reports and their history, links might be broken. See T68608, the corresponding Phabricator task for complete and up-to-date bug report information.
Bug 66608 - XSS in mediawiki.page.image.pagination.js
XSS in mediawiki.page.image.pagination.js
Status: RESOLVED FIXED
Product: MediaWiki
Classification: Unclassified
Interface (Other open bugs)
1.24rc
All All
: Highest normal (vote)
: 1.24.0 release
Assigned To: Nobody - You can work on this!
:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2014-06-14 08:01 UTC by Michael M.
Modified: 2014-08-16 10:02 UTC (History)
9 users (show)

See Also:
Web browser: ---
Mobile Platform: ---
Assignee Huggle Beta Tester: ---


Attachments
quick hack to close the XSS vulnerability, at least with WMF-like config (2.43 KB, patch)
2014-06-17 00:30 UTC, Tisza Gergő
Details
Generate URL on the client side (3.34 KB, patch)
2014-06-17 19:35 UTC, Tisza Gergő
Details
Generate URL on the client side (2.69 KB, patch)
2014-06-27 00:25 UTC, Tisza Gergő
Details
Backport to REL1_23 (2.73 KB, patch)
2014-07-29 13:30 UTC, Markus Glaser
Details
Backport to REL1_22 (2.71 KB, patch)
2014-07-29 13:31 UTC, Markus Glaser
Details

Description Michael M. 2014-06-14 08:01:52 UTC
Steps to reproduce:

0. Make sure you have a wiki with upload and thumbnailing of paged media enabled (see [[mw:Extension:PdfHandler]] if you don't), and a multipaged file (I will call this File:Carroll.pdf in the following).
1. Create File:Carroll.pdf/x with the content

<table class="multipageimage"><tr>
<img src="http://this.is.invalid/a.png" onerror="alert('XSS')">
</tr></table>

2. Edit File:Carroll.pdf to have the following content as description

<div class="multipageimagenavbox">[{{fullurl:File:Carroll.pdf/x|action=raw}} Click me!]</div>

and save it (you can't reproduce this in preview).

3. Click on the link.

Expected result: Nothing evil happens.
Actual result: A message box pops up, showing "XSS".

The problem is in resources/src/mediawiki.page/mediawiki.page.image.pagination.js, it trusts any link inside an element with class multipageimagenavbox. The function ajaxifyPageNavigation() will call loadPage with the URL of such an link once you click it, and the loadPage function will load that content and interpret it as HTML. (You can't embed <script> tags, as some jQuery magic will remove them, but this isn't necessary as shown in my example.)

Possible solution:

Add a class multipageimagenavboxlink to the <a> tags that should trigger the AJAX navigation, and use $( 'a.multipageimagenavboxlink' ).one( ... ); instead. As there is no way to add links with arbitrary classes in wikitext, such a link can be trusted to be provided by MediaWiki itself.
Comment 1 Chris Steipp 2014-06-16 18:12:43 UTC
Thanks for the report and research Michael M! I'm able to reproduce this on my dev instance.

Gergő, is this an area of code you can help write a patch for? I'm not sure what is supposed to be in the expected data that is loaded in (line 46), so I'm not sure if we can best sanitize that.
Comment 2 Tisza Gergő 2014-06-16 22:44:26 UTC
(In reply to Chris Steipp from comment #1)
> Gergő, is this an area of code you can help write a patch for? I'm not sure
> what is supposed to be in the expected data that is loaded in (line 46), so
> I'm not sure if we can best sanitize that.

I'll check. The data contains a HTML snippet with preview of the current/prev/next page and associated links, I don't think it can be safely sanitized. The fix suggested by Michael sounds OK for Wikimedia sites; I'm not sure "links cannot have custom classes" is an assumption we would want to base an XSS protection on. Sanitizing the URL is another possibility, although it might conflict with nice URL schemes.
Comment 3 Tisza Gergő 2014-06-17 00:30:46 UTC
Created attachment 15670 [details]
quick hack to close the XSS vulnerability, at least with WMF-like config

The relevant parts for creating the HTML source for the prev/next page thumbnails are ImagePage::openShowImage lines 470/490. Adding a class to the links in the thumbnail captions is easy, but there doesn't seem to be any sane way to add a class or other markup to the links which wrap the thumbnail images. Attached a patch which uses an ugly workaround to get around this.
Comment 4 Tisza Gergő 2014-06-17 00:35:57 UTC
A nicer and more generic solution could be to dump an array of safe URLs into the page (as a JS variable), and use that to verify that the link has not been tempered with.
Comment 5 Michael M. 2014-06-17 10:49:35 UTC
What about building the link in the JS code instead of retrieving it from the HTML?

safeHref = mw.util.getUrl( mw.config.get( 'wgPageName' ), {
 page: mw.util.getParamValue( 'page', this.href )
} );

"page" seems to be the only parameter, and any url with only "page" as parameter should be safe, even if a user manages to sneak an invalid value for page in.
Comment 6 Chris Steipp 2014-06-17 17:20:12 UTC
I like how simple Michael M's solution is, and it seems like that will minimize the possible confusion. I also added a conversion to int, just to clarify that's what we expect (and worst case it passes page=NaN in the url).

$( '.multipageimagenavbox' ).one( 'click', 'a', function ( e ) {
	loadPage( mw.util.getUrl( mw.config.get( 'wgPageName' ), {
		page: +( mw.util.getParamValue( 'page', this.href ) )
	} ) );
	e.preventDefault();
} );

This looks like it works for my simple test cases, is commons doing anything odd that might have relied on the old functionality?
Comment 7 Tisza Gergő 2014-06-17 18:06:54 UTC
This will result in a different URL than the one used in the <a> tag, and I am not sure it will work with sufficiently funny file names. Compare:

2106643628).jpg?action=raw">https://commons.wikimedia.org/wiki/File:%3F_and_Michael_(2106643628).jpg?action=raw
2106643628).jpg&action=raw">https://commons.wikimedia.org/w/index.php?title=File:%3F_and_Michael_(2106643628).jpg&action=raw

Something like

loadPage( mw.config.get( 'wgScript' ) + '?' +  $.param( {
    title: mw.config.get( 'wgPageName' ),
    page: +( mw.util.getParamValue( 'page', this.href ) )
} ) );

would work though.
Comment 8 Tisza Gergő 2014-06-17 18:09:29 UTC
Let me try that again: http://pastebin.com/raw.php?i=K9x9FaYp
Comment 9 Krinkle 2014-06-17 18:17:30 UTC
I'm not sure how this vulnerability would be make possible.


Though I think the mediawiki.page.image.pagination.js module is not up to our standards and merged too early, it doesn't interpret text or wikitext as HTML or anything. It is only parsing as HTML was our servers are serving as HTML in the first place.

Since it only loads content from our own domain, a third party url has no influence, so the snippet with <img onerror> would have to be on our own domain. The writer suggests on a subpage "/x" of the file description page. Presumably as wikitext.

Well, then there is your problem and solution.

1) We don't allow <img> in the wikitext anyway. If you do, it'll just render as plain text. No problem here.

2) If a third-party wiki has chosen to configure their wiki and allow <img> tags the Sanitizer still doens't allow inline javascript event handlers (of obvious reasons), so "onerror" would just be stripped far before mediawiki.page.image.pagination.js comes into play.

Ah, I see now it is using action=raw (which serves the unparsed wiki text as plain text, not html). So another thing we should do better in general is honour the Content-Type header of our server and not unconditionally parse it as HTML.

$.ajax /X?action=raw
getAllResponseHeaders()
 - Content-Type: text/x-wiki; charset=UTF-8


And yeah, I agree the url shouldn't be retrieved in this fashion (unless the css selector is restricted to scope outside page content). I'd recommend either parsing the url and extracting the 'page' parameter only, or using data attributes.
Comment 10 Krinkle 2014-06-17 18:25:27 UTC
(In reply to Tisza Gergő from comment #8)
> Let me try that again: http://pastebin.com/raw.php?i=K9x9FaYp

(In reply to Tisza Gergő from comment #7)
> Something like
> 
> loadPage( mw.config.get( 'wgScript' ) + '?' +  $.param( {
>     title: mw.config.get( 'wgPageName' ),
>     page: +( mw.util.getParamValue( 'page', this.href ) )
> } ) );
> 
> would work though.

Note that with $.param() it does not use the appropriate url encoding for the page title, as a result the URLs will look ugly and not match the canonical url.


url = new mw.Uri( mw.util.wikiScript() )
  .extend({ title: mw.config.get( 'wgPageName' ), page: 2 })
  .toString();

> "https://commons.wikimedia.org/w/index.php?title=File:Example.jpg&page=2"

url = mw.util.wikiScript() + '?' + $.param({
   title: mw.config.get( 'wgPageName' ),
   page: 2
  });

> "/w/index.php?title=File%3AExample.jpg&page=2"

url = mw.util.getUrl(mw.config.get( 'wgPageName' ), {
   page: 2
  });

> "/wiki/File:Example.jpg?page=2"

Notice the garbled colon with '%3A'. In foreign languages and more complex page names this gets exponentially worse.

It works as a quick hack, but it should never pass code review. To make it match server-side behaviour I'd recommend using mw.Uri (be sure to add "dependency => mediawiki.Uri" for this module, ResourceLoader takes care of the rest).

You may also want to add a comment pointing to ImagePage::openShowImage saying this should be kept in sync with that (and visa versa) since we're not hardcoding something we kinda shouldn't.
Comment 11 Tisza Gergő 2014-06-17 19:35:04 UTC
Created attachment 15676 [details]
Generate URL on the client side

New patch based on Krinkle's suggestions.
Comment 12 Chris Steipp 2014-06-17 19:48:13 UTC
Thanks Gergo, that patch works looks good to me. Krinkle, if that seems ok to you, I can deploy it this afternoon.
Comment 13 Krinkle 2014-06-26 22:45:47 UTC
Looks good in principle, though one important and one less important point:

diff --git a/resources/Resources.php b/resources/Resources.php
index ea7d397..230a652 100644
@@ -1220,7 +1220,10 @@ return array(
 	),
 	'mediawiki.page.image.pagination' => array(
 		'scripts' => 'resources/src/mediawiki.page/mediawiki.page.image.pagination.js',
-		'dependencies' => array( 'jquery.spinner' )
+		'dependencies' => array(
+			'mediawiki.Uri',
+			'jquery.spinner',
+		)
 	),
 

This also needs a dependency on mediawiki.util since the code introduces a call to mw.util.getParamValue

diff --git a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js b/resources/src/mediawiki.page/mediawiki.page.image.pagination.js
index 4819be0..1c1feed 100644
+			page = +( mw.util.getParamValue( 'page', this.href ) );

Coding style, use the Number() number to explicitly cast to a number (currently our minifier keeps this, but UglifyJS minifies it down to +).

--
Comment 14 Tisza Gergő 2014-06-27 00:25:14 UTC
Created attachment 15758 [details]
Generate URL on the client side

Rebased patch and fixed the issues mentioned by Timo.

After Timo's refactoring of mediawiki.page.image.pagination.js, the original exploit does not work anymore. To reproduce, use this code for step 2:

{| class="multipageimage"
<div class="multipageimagenavbox">[{{fullurl:File:Carroll.pdf/x|action=raw}} Click me!]</div>
|}
Comment 15 Markus Glaser 2014-07-29 13:30:55 UTC
Created attachment 16085 [details]
Backport to REL1_23

This is a backport to REL1_23. If someone could "+2", I'd be very thankful.
Comment 16 Markus Glaser 2014-07-29 13:31:20 UTC
Created attachment 16086 [details]
Backport to REL1_22

This is a backport to REL1_23. If someone could "+2", I'd be very thankful.
Comment 17 Markus Glaser 2014-07-29 13:32:13 UTC
In REL1_19, there's no mediawiki.page.image.pagination.js. I assume, a backport is not neccessary, right?
Comment 18 Chris Steipp 2014-07-29 16:39:34 UTC
It was introduced by Icd1cde7c62c4d462f5b697b9f49f5c08f6e7482b (1.22wmf14), so 1.19 isn't affected.

Adding Wikia and debian.
Comment 19 Chris Steipp 2014-07-29 16:40:47 UTC
(In reply to Chris Steipp from comment #18)
> Adding Wikia and debian.

Markus is planning to release this in the next security release (probably tomorrow, July 30th).
Comment 20 Chris Steipp 2014-07-30 00:26:04 UTC
Both backports fix the issue on my dev instances, and don't seem to be causing issues. +2.
Comment 21 Markus Glaser 2014-07-31 14:46:35 UTC
Moved to MediaWiki product as fix is now public.
Comment 22 Chris Steipp 2014-08-14 16:32:01 UTC
This was assigned CVE-2014-5242
Comment 23 Andre Klapper 2014-08-16 10:02:02 UTC
Fix included in 1.23.2 and 1.22.9 tarballs; fixed in https://git.wikimedia.org/commit/mediawiki%2Fcore.git/410d33d36e9a4fe28f54aa4347310c52762b6e5e

Hence closing ticket as FIXED (but please reopen if I missed something).

Note You need to log in before you can comment on or make changes to this bug.


Navigation
Links