SECURITY: Disallow loading JS/CSS/Json subpages from unregistered users and log
authorBrian Wolff <bawolff+wn@gmail.com>
Tue, 15 May 2018 00:34:14 +0000 (00:34 +0000)
committerPaladox <thomasmulhall410@yahoo.com>
Sun, 21 Oct 2018 18:09:31 +0000 (19:09 +0100)
Loading JS from an unregistered user's JS subpage is a severe
security risk as someone could potentially register that account
and then modify the JS.

Bug: T207603
Change-Id: I741736e12b0ed49e95f22c869a2b53e2c97b31f0

RELEASE-NOTES-1.31
includes/actions/RawAction.php
languages/i18n/en.json
languages/i18n/qqq.json

index 2245aba..3e7f5d1 100644 (file)
@@ -16,6 +16,8 @@ THIS IS NOT A RELEASE YET
 * Fix PHP 7.3 warnings "preg_replace(): [...] invalid range in character class"
 * (T207540) Include IP address in "Login for $1 succeeded" log entry.
 * (T207541) Pass email address to mail().
+* (T207603) User JS may no longer be loaded with mime type text/javascript if
+  there is no account associated with the username.
 
 == MediaWiki 1.31.1 ==
 
index 812f962..159e708 100644 (file)
@@ -26,6 +26,8 @@
  * @file
  */
 
+use MediaWiki\Logger\LoggerFactory;
+
 /**
  * A simple method to retrieve the plain source of an article,
  * using "action=raw" in the GET request string.
@@ -85,7 +87,6 @@ class RawAction extends FormlessAction {
                        $response->header( $this->getOutput()->getKeyHeader() );
                }
 
-               $response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' );
                // Output may contain user-specific data;
                // vary generated content for open sessions on private wikis
                $privateCache = !User::isEveryoneAllowed( 'read' ) &&
@@ -97,6 +98,33 @@ class RawAction extends FormlessAction {
                        'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage
                );
 
+               // In the event of user JS, don't allow loading a user JS/CSS/Json
+               // subpage that has no registered user associated with, as
+               // someone could register the account and take control of the
+               // JS/CSS/Json page.
+               $title = $this->getTitle();
+               if ( $title->isUserConfigPage() && $contentType !== 'text/x-wiki' ) {
+                       // not using getRootText() as we want this to work
+                       // even if subpages are disabled.
+                       $rootPage = strtok( $title->getText(), '/' );
+                       $userFromTitle = User::newFromName( $rootPage, 'usable' );
+                       if ( !$userFromTitle || $userFromTitle->getId() === 0 ) {
+                               $log = LoggerFactory::getInstance( "security" );
+                               $log->warning(
+                                       "Unsafe JS/CSS/Json load - {user} loaded {title} with {ctype}",
+                                       [
+                                               'user' => $this->getUser()->getName(),
+                                               'title' => $title->getPrefixedDBKey(),
+                                               'ctype' => $contentType,
+                                       ]
+                               );
+                               $msg = wfMessage( 'unregistered-user-config' );
+                               throw new HttpError( 403, $msg );
+                       }
+               }
+
+               $response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' );
+
                $text = $this->getRawText();
 
                // Don't return a 404 response for CSS or JavaScript;
index 7374f38..89564e0 100644 (file)
        "pagedata-title": "Page data",
        "pagedata-text": "This page provides a data interface to pages. Please provide the page title in the URL, using subpage syntax.\n* Content negotiation applies based on your client's Accept header. This means that the page data will be provided in the format preferred by your client.",
        "pagedata-not-acceptable": "No matching format found. Supported MIME types: $1",
-       "pagedata-bad-title": "Invalid title: $1."
+       "pagedata-bad-title": "Invalid title: $1.",
+       "unregistered-user-config": "For security reasons JavaScript, CSS and JSON user subpages cannot be loaded for unregistered users."
 }
index 760d37d..8e8372b 100644 (file)
        "pagedata-title": "Title shown on the special page when a form or text is presented",
        "pagedata-text": "Error shown when none of the formats acceptable to the client is supported (HTTP error 406). Parameters:\n* $1 - the list of supported MIME types",
        "pagedata-not-acceptable": "No matching format found. Supported MIME types: $1",
-       "pagedata-bad-title": "Error shown when the requested title is invalid. Parameters:\n* $1: the malformed ID"
+       "pagedata-bad-title": "Error shown when the requested title is invalid. Parameters:\n* $1: the malformed ID",
+       "unregistered-user-config": "Shown when viewing a user JS, CSS or JSON subpage with ?action=raw&ctype=<mime type> where there is no such user. It is shown as a paragraph after a header saying 'Forbidden'."
 }