SECURITY: Make Special:MyPage and friends fake redirect to prevent info leak
authorcsteipp <csteipp@wikimedia.org>
Mon, 5 Oct 2015 23:58:42 +0000 (16:58 -0700)
committerChad Horohoe <chadh@wikimedia.org>
Fri, 18 Dec 2015 09:46:03 +0000 (01:46 -0800)
This prevents a malicious person from using external resources on their
website to cause the victim's web browser to load
Special:MyPage -> User:Username, and then looking it up in the page hit
statistics in order to correlate IPs from the malicious person's server
log, with usernames on wiki.

This feature can be disabled with $wgHideIdentifiableRedirects.

Bug: T109724
Signed-off-by: Chad Horohoe <chadh@wikimedia.org>
Change-Id: Ia0e742dc92c77af4832174dfa24c6dcaa6ee80e9

includes/DefaultSettings.php
includes/MediaWiki.php
includes/specialpage/RedirectSpecialPage.php
includes/specials/SpecialMyLanguage.php
includes/specials/SpecialMyRedirectPages.php

index f55cb56..54216fd 100644 (file)
@@ -4754,6 +4754,12 @@ $wgWhitelistReadRegexp = false;
  */
 $wgEmailConfirmToEdit = false;
 
+/**
+ * Should MediaWiki attempt to protect user's privacy when doing redirects?
+ * Keep this true if access counts to articles are made public.
+ */
+$wgHideIdentifiableRedirects = true;
+
 /**
  * Permission keys given to users in each group.
  *
index fe1bb8e..19a0df7 100644 (file)
@@ -36,6 +36,11 @@ class MediaWiki {
         */
        private $config;
 
+       /**
+        * @var String Cache what action this request is
+        */
+       private $action;
+
        /**
         * @param IContextSource|null $context
         */
@@ -141,13 +146,11 @@ class MediaWiki {
         * @return string Action
         */
        public function getAction() {
-               static $action = null;
-
-               if ( $action === null ) {
-                       $action = Action::getActionName( $this->context );
+               if ( $this->action === null ) {
+                       $this->action = Action::getActionName( $this->context );
                }
 
-               return $action;
+               return $this->action;
        }
 
        /**
@@ -241,8 +244,37 @@ class MediaWiki {
                // Handle any other redirects.
                // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant
                } elseif ( !$this->tryNormaliseRedirect( $title ) ) {
+                       // Prevent information leak via Special:MyPage et al (T109724)
+                       if ( $title->isSpecialPage() ) {
+                               $specialPage = SpecialPageFactory::getPage( $title->getDBKey() );
+                               if ( $specialPage instanceof RedirectSpecialPage
+                                       && $this->config->get( 'HideIdentifiableRedirects' )
+                                       && $specialPage->personallyIdentifiableTarget()
+                               ) {
+                                       list( , $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBKey() );
+                                       $target = $specialPage->getRedirect( $subpage );
+                                       // target can also be true. We let that case fall through to normal processing.
+                                       if ( $target instanceof Title ) {
+                                               $query = $specialPage->getRedirectQuery() ?: array();
+                                               $request = new DerivativeRequest( $this->context->getRequest(), $query );
+                                               $request->setRequestURL( $this->context->getRequest()->getRequestURL() );
+                                               $this->context->setRequest( $request );
+                                               // Do not varnish cache these. May vary even for anons
+                                               $this->context->getOutput()->lowerCdnMaxage( 0 );
+                                               $this->context->setTitle( $target );
+                                               $wgTitle = $target;
+                                               // Reset action type cache. (Special pages have only view)
+                                               $this->action = null;
+                                               $title = $target;
+                                               $output->addJsConfigVars( array(
+                                                       'wgInternalRedirectTargetUrl' => $target->getFullURL(),
+                                               ) );
+                                               $output->addModules( 'mediawiki.action.view.redirect' );
+                                       }
+                               }
+                       }
 
-                       // Special pages
+                       // Special pages ($title may have changed since if statement above)
                        if ( NS_SPECIAL == $title->getNamespace() ) {
                                // Actions that need to be made when we have a special pages
                                SpecialPageFactory::executePath( $title, $this->context );
index 9129ee5..5047354 100644 (file)
@@ -94,6 +94,18 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
                        ? $params
                        : false;
        }
+
+       /**
+        * Indicate if the target of this redirect can be used to identify
+        * a particular user of this wiki (e.g., if the redirect is to the
+        * user page of a User). See T109724.
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return false;
+       }
 }
 
 /**
index 3d8ff97..d11fbe6 100644 (file)
@@ -99,4 +99,15 @@ class SpecialMyLanguage extends RedirectSpecialArticle {
                        return $base;
                }
        }
+
+       /**
+        * Target can identify a specific user's language preference.
+        *
+        * @see T109724
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return true;
+       }
 }
index 5ef03f1..850b1f6 100644 (file)
@@ -45,6 +45,16 @@ class SpecialMypage extends RedirectSpecialArticle {
 
                return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
        }
+
+       /**
+        * Target identifies a specific User. See T109724.
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return true;
+       }
 }
 
 /**
@@ -68,6 +78,16 @@ class SpecialMytalk extends RedirectSpecialArticle {
 
                return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
        }
+
+       /**
+        * Target identifies a specific User. See T109724.
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return true;
+       }
 }
 
 /**
@@ -90,6 +110,16 @@ class SpecialMycontributions extends RedirectSpecialPage {
        public function getRedirect( $subpage ) {
                return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
        }
+
+       /**
+        * Target identifies a specific User. See T109724.
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return true;
+       }
 }
 
 /**
@@ -110,6 +140,16 @@ class SpecialMyuploads extends RedirectSpecialPage {
        public function getRedirect( $subpage ) {
                return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
        }
+
+       /**
+        * Target identifies a specific User. See T109724.
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return true;
+       }
 }
 
 /**
@@ -132,4 +172,14 @@ class SpecialAllMyUploads extends RedirectSpecialPage {
 
                return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
        }
+
+       /**
+        * Target identifies a specific User. See T109724.
+        *
+        * @since 1.27
+        * @return bool
+        */
+       public function personallyIdentifiableTarget() {
+               return true;
+       }
 }