From 23e39ce9aaf29392bc0dd8dbe724246d6af7dfdf Mon Sep 17 00:00:00 2001 From: Brian Wolff Date: Sun, 7 Feb 2016 08:07:20 -0500 Subject: [PATCH] Do not directly redirect to interwikis, but use splash page Directly redirecting based on a url paramter might potentially be used in a phishing attack to confuse users. Bug: T109140 Bug: T122209 Change-Id: I6c604439320fa876719933cc7f3a3ff04fb1a6ad --- autoload.php | 1 + includes/OutputPage.php | 4 +- includes/Title.php | 27 ++++++++++ includes/specialpage/RedirectSpecialPage.php | 2 +- includes/specialpage/SpecialPageFactory.php | 1 + includes/specials/SpecialChangeEmail.php | 2 +- includes/specials/SpecialChangePassword.php | 2 +- includes/specials/SpecialGoToInterwiki.php | 79 ++++++++++++++++++++++++++++ includes/specials/SpecialPageLanguage.php | 2 +- includes/specials/SpecialPreferences.php | 2 +- includes/specials/SpecialSearch.php | 2 +- includes/specials/SpecialUserlogin.php | 2 +- languages/i18n/en.json | 5 +- languages/i18n/qqq.json | 5 +- languages/messages/MessagesEn.php | 1 + 15 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 includes/specials/SpecialGoToInterwiki.php diff --git a/autoload.php b/autoload.php index 8720186..bed236a 100644 --- a/autoload.php +++ b/autoload.php @@ -1171,6 +1171,7 @@ $wgAutoloadLocalClasses = array( 'SpecialExpandTemplates' => __DIR__ . '/includes/specials/SpecialExpandTemplates.php', 'SpecialExport' => __DIR__ . '/includes/specials/SpecialExport.php', 'SpecialFilepath' => __DIR__ . '/includes/specials/SpecialFilepath.php', + 'SpecialGoToInterwiki' => __DIR__ . '/includes/specials/SpecialGoToInterwiki.php', 'SpecialImport' => __DIR__ . '/includes/specials/SpecialImport.php', 'SpecialJavaScriptTest' => __DIR__ . '/includes/specials/SpecialJavaScriptTest.php', 'SpecialListAdmins' => __DIR__ . '/includes/specials/SpecialListusers.php', diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 97165b4..dcf835c 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2628,7 +2628,9 @@ class OutputPage extends ContextSource { } else { $titleObj = Title::newFromText( $returnto ); } - if ( !is_object( $titleObj ) ) { + // We don't want people to return to external interwiki. That + // might potentially be used as part of a phishing scheme + if ( !is_object( $titleObj ) || $titleObj->isExternal() ) { $titleObj = Title::newMainPage(); } diff --git a/includes/Title.php b/includes/Title.php index 882b7dd..d600343 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1692,6 +1692,33 @@ class Title { } /** + * Get a url appropriate for making redirects based on an untrusted url arg + * + * This is basically the same as getFullUrl(), but in the case of external + * interwikis, we send the user to a landing page, to prevent possible + * phishing attacks and the like. + * + * @note Uses current protocol by default, since technically relative urls + * aren't allowed in redirects per HTTP spec, so this is not suitable for + * places where the url gets cached, as might pollute between + * https and non-https users. + * @see self::getLocalURL for the arguments. + * @param array|string $query + * @param string $proto Protocol type to use in URL + * @return String. A url suitable to use in an HTTP location header. + */ + public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) { + $target = $this; + if ( $this->isExternal() ) { + $target = SpecialPage::getTitleFor( + 'GoToInterwiki', + $this->getPrefixedDBKey() + ); + } + return $target->getFullUrl( $query, false, $proto ); + } + + /** * Get a URL with no fragment or server name (relative URL) from a Title object. * If this page is generated with action=render, however, * $wgServer is prepended to make an absolute URL. diff --git a/includes/specialpage/RedirectSpecialPage.php b/includes/specialpage/RedirectSpecialPage.php index 5047354..1d0a653 100644 --- a/includes/specialpage/RedirectSpecialPage.php +++ b/includes/specialpage/RedirectSpecialPage.php @@ -41,7 +41,7 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage { $query = $this->getRedirectQuery(); // Redirect to a page title with possible query parameters if ( $redirect instanceof Title ) { - $url = $redirect->getFullURL( $query ); + $url = $redirect->getFullUrlForRedirect( $query ); $this->getOutput()->redirect( $url ); return $redirect; diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php index 3babafd..53832b9 100644 --- a/includes/specialpage/SpecialPageFactory.php +++ b/includes/specialpage/SpecialPageFactory.php @@ -135,6 +135,7 @@ class SpecialPageFactory { 'RandomInCategory' => 'SpecialRandomInCategory', 'Randomredirect' => 'SpecialRandomredirect', 'Randomrootpage' => 'SpecialRandomrootpage', + 'GoToInterwiki' => 'SpecialGoToInterwiki', // High use pages 'Mostlinkedcategories' => 'MostlinkedCategoriesPage', diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php index 989bae9..7c89aa0 100644 --- a/includes/specials/SpecialChangeEmail.php +++ b/includes/specials/SpecialChangeEmail.php @@ -146,7 +146,7 @@ class SpecialChangeEmail extends FormSpecialPage { $query = $request->getVal( 'returntoquery' ); if ( $this->status->value === true ) { - $this->getOutput()->redirect( $titleObj->getFullURL( $query ) ); + $this->getOutput()->redirect( $titleObj->getFullUrlForRedirect( $query ) ); } elseif ( $this->status->value === 'eauth' ) { # Notify user that a confirmation email has been sent... $this->getOutput()->wrapWikiMsg( "
\n$1\n
", diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php index 8656798..bf7f328 100644 --- a/includes/specials/SpecialChangePassword.php +++ b/includes/specials/SpecialChangePassword.php @@ -192,7 +192,7 @@ class SpecialChangePassword extends FormSpecialPage { $titleObj = Title::newMainPage(); } $query = $request->getVal( 'returntoquery' ); - $this->getOutput()->redirect( $titleObj->getFullURL( $query ) ); + $this->getOutput()->redirect( $titleObj->getFullUrlForRedirect( $query ) ); return true; } diff --git a/includes/specials/SpecialGoToInterwiki.php b/includes/specials/SpecialGoToInterwiki.php new file mode 100644 index 0000000..809a14a --- /dev/null +++ b/includes/specials/SpecialGoToInterwiki.php @@ -0,0 +1,79 @@ +setHeaders(); + $target = Title::newFromText( $par ); + // Disallow special pages as a precaution against + // possible redirect loops. + if ( !$target || $target->isSpecialPage() ) { + $this->getOutput()->setStatusCode( 404 ); + $this->getOutput()->addWikiMsg( 'gotointerwiki-invalid' ); + return; + } + + $url = $target->getFullURL(); + if ( !$target->isExternal() || $target->isLocal() ) { + // Either a normal page, or a local interwiki. + // just redirect. + $this->getOutput()->redirect( $url, '301' ); + } else { + $this->getOutput()->addWikiMsg( + 'gotointerwiki-external', + $url, + $target->getFullText() + ); + } + } + + /** + * @return bool + */ + public function requiresWrite() { + return false; + } + + /** + * @return String + */ + protected function getGroupName() { + return 'redirects'; + } +} diff --git a/includes/specials/SpecialPageLanguage.php b/includes/specials/SpecialPageLanguage.php index 69a9d48..8ba2524 100644 --- a/includes/specials/SpecialPageLanguage.php +++ b/includes/specials/SpecialPageLanguage.php @@ -142,7 +142,7 @@ class SpecialPageLanguage extends FormSpecialPage { ); // Url to redirect to after the operation - $this->goToUrl = $title->getFullURL(); + $this->goToUrl = $title->getFullUrlForRedirect(); // Check if user wants to use default language if ( $data['selectoptions'] == 1 ) { diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index 965a36e..960738e 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -143,7 +143,7 @@ class SpecialPreferences extends SpecialPage { // Set session data for the success message $this->getRequest()->setSessionData( 'specialPreferencesSaveSuccess', 1 ); - $url = $this->getPageTitle()->getFullURL(); + $url = $this->getPageTitle()->getFullUrlForRedirect(); $this->getOutput()->redirect( $url ); return true; diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index 8809900..89e3733 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -205,7 +205,7 @@ class SpecialSearch extends SpecialPage { $title = SearchEngine::getNearMatch( $term ); if ( !is_null( $title ) ) { - $this->getOutput()->redirect( $title->getFullURL() ); + $this->getOutput()->redirect( $title->getFullUrlForRedirect() ); return; } diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 62e200e..a4d5ff5 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -1330,7 +1330,7 @@ class LoginForm extends SpecialPage { } if ( $type == 'successredirect' ) { - $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto ); + $redirectUrl = $returnToTitle->getFullUrlForRedirect( $returnToQuery, $proto ); $this->getOutput()->redirect( $redirectUrl ); } else { $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options ); diff --git a/languages/i18n/en.json b/languages/i18n/en.json index cb9f7c4..c0d0e24 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -3957,5 +3957,8 @@ "mw-widgets-titleinput-description-new-page": "page does not exist yet", "mw-widgets-titleinput-description-redirect": "redirect to $1", "api-error-blacklisted": "Please choose a different, descriptive title.", - "randomrootpage": "Random root page" + "randomrootpage": "Random root page", + "gotointerwiki": "Leaving {{SITENAME}}", + "gotointerwiki-invalid": "The specified title was invalid.", + "gotointerwiki-external": "You are about to leave {{SITENAME}} to visit [[$2]] which is a separate website.\n\n[$1 Click here to continue on to $1]." } diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 388ec67..81d5c4b 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -4132,5 +4132,8 @@ "mw-widgets-titleinput-description-new-page": "Description label for a new page in the title input widget.", "mw-widgets-titleinput-description-redirect": "Description label for a redirect in the title input widget.", "api-error-blacklisted": "Used as error message.\n\nFollowed by the link {{msg-mw|Mwe-upwiz-feedback-blacklist-info-prompt}}.", - "randomrootpage": "{{doc-special|RandomRootPage}}" + "randomrootpage": "{{doc-special|RandomRootPage}}", + "gotointerwiki": "{{doc-special|GoToInterwiki}}\n\nSpecial:GoToInterwiki is a warning page displayed before redirecting users to external interwiki links. Its triggered by people going to something like [[Special:Search/google:foo]].", + "gotointerwiki-invalid": "Message shown on Special:GoToInterwiki if given an invalid title.", + "gotointerwiki-external": "Message shown on Special:GoToInterwiki if given a external interwiki link (e.g. [[Special:GoToInterwiki/Google:Foo]]). $1 is the full url the user is trying to get to. $2 is the text of the interwiki link (e.g.\ "Google:foo\")." } diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 8fa13c6..fff8ee3 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -416,6 +416,7 @@ $specialPageAliases = array( 'Fewestrevisions' => array( 'FewestRevisions' ), 'FileDuplicateSearch' => array( 'FileDuplicateSearch' ), 'Filepath' => array( 'FilePath' ), + 'GoToInterwiki' => array( 'GoToInterwiki' ), 'Import' => array( 'Import' ), 'Invalidateemail' => array( 'InvalidateEmail' ), 'JavaScriptTest' => array( 'JavaScriptTest' ), -- 2.0.1