From 0066c796ca1c5a13b7f3a76c1da4927f764610a8 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/SpecialChangeCredentials.php | 2 +- includes/specials/SpecialChangeEmail.php | 2 +- includes/specials/SpecialGoToInterwiki.php | 79 ++++++++++++++++++++++ includes/specials/SpecialPageLanguage.php | 2 +- includes/specials/SpecialPreferences.php | 2 +- includes/specials/SpecialSearch.php | 2 +- includes/specials/helpers/LoginHelper.php | 2 +- .../specials/pre-authmanager/SpecialUserlogin.php | 2 +- languages/i18n/en.json | 5 +- languages/i18n/qqq.json | 5 +- languages/messages/MessagesEn.php | 1 + 16 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 includes/specials/SpecialGoToInterwiki.php diff --git a/autoload.php b/autoload.php index 29f986e..4996e6c 100644 --- a/autoload.php +++ b/autoload.php @@ -1295,6 +1295,7 @@ $wgAutoloadLocalClasses = [ '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', 'SpecialLinkAccounts' => __DIR__ . '/includes/specials/SpecialLinkAccounts.php', diff --git a/includes/OutputPage.php b/includes/OutputPage.php index b6c48ab..d3bf747 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2604,7 +2604,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 ea42768..1fb1d1e 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1663,6 +1663,33 @@ class Title implements LinkTarget { } /** + * 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 ea7d783..01787d3 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 b69b28a..7773f73 100644 --- a/includes/specialpage/SpecialPageFactory.php +++ b/includes/specialpage/SpecialPageFactory.php @@ -144,6 +144,7 @@ class SpecialPageFactory { 'RandomInCategory' => 'SpecialRandomInCategory', 'Randomredirect' => 'SpecialRandomredirect', 'Randomrootpage' => 'SpecialRandomrootpage', + 'GoToInterwiki' => 'SpecialGoToInterwiki', // High use pages 'Mostlinkedcategories' => 'MostlinkedCategoriesPage', diff --git a/includes/specials/SpecialChangeCredentials.php b/includes/specials/SpecialChangeCredentials.php index 4da8049..55bbb9b 100644 --- a/includes/specials/SpecialChangeCredentials.php +++ b/includes/specials/SpecialChangeCredentials.php @@ -233,7 +233,7 @@ class SpecialChangeCredentials extends AuthManagerSpecialPage { } $title = Title::newFromText( $returnTo ); - return $title->getFullURL( $returnToQuery ); + return $title->getFullUrlForRedirect( $returnToQuery ); } protected function getRequestBlacklist() { diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php index 785447f..eb98fe7 100644 --- a/includes/specials/SpecialChangeEmail.php +++ b/includes/specials/SpecialChangeEmail.php @@ -136,7 +136,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/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 5322a04..0035829 100644 --- a/includes/specials/SpecialPageLanguage.php +++ b/includes/specials/SpecialPageLanguage.php @@ -141,7 +141,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 f00477f..8366c22 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -148,7 +148,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 9690d45..2387ec0 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -218,7 +218,7 @@ class SpecialSearch extends SpecialPage { Hooks::run( 'SpecialSearchGoResult', [ $term, $title, &$url ] ) ) { if ( $url === null ) { - $url = $title->getFullURL(); + $url = $title->getFullUrlForRedirect(); } $this->getOutput()->redirect( $url ); diff --git a/includes/specials/helpers/LoginHelper.php b/includes/specials/helpers/LoginHelper.php index f853f41..37aebc2 100644 --- a/includes/specials/helpers/LoginHelper.php +++ b/includes/specials/helpers/LoginHelper.php @@ -89,7 +89,7 @@ class LoginHelper extends ContextSource { } if ( $type === 'successredirect' ) { - $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto ); + $redirectUrl = $returnToTitle->getFullUrlForRedirect( $returnToQuery, false, $proto ); $this->getOutput()->redirect( $redirectUrl ); } else { $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options ); diff --git a/includes/specials/pre-authmanager/SpecialUserlogin.php b/includes/specials/pre-authmanager/SpecialUserlogin.php index 09132f2..7a57dbc 100644 --- a/includes/specials/pre-authmanager/SpecialUserlogin.php +++ b/includes/specials/pre-authmanager/SpecialUserlogin.php @@ -1422,7 +1422,7 @@ class LoginFormPreAuthManager 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 bc7d78d..5d7407c 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -4193,5 +4193,8 @@ "linkaccounts-submit": "Link accounts", "unlinkaccounts": "Unlink accounts", "unlinkaccounts-success": "The account was unlinked.", - "authenticationdatachange-ignored": "The authentication data change was not handled. Maybe no provider was configured?" + "authenticationdatachange-ignored": "The authentication data change was not handled. Maybe no provider was configured?", + "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 b553756..fb27a57 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -4375,5 +4375,8 @@ "linkaccounts-submit": "Text of the main submit button on [[Special:LinkAccounts]] (when there is one)", "unlinkaccounts": "Title of the special page [[Special:UnlinkAccounts]] which allows the user to remove linked remote accounts.", "unlinkaccounts-success": "Account unlinking form success message", - "authenticationdatachange-ignored": "Shown when authentication data change was unsuccessful due to configuration problems.\n\nCf. e.g. {{msg-mw|Passwordreset-ignored}}." + "authenticationdatachange-ignored": "Shown when authentication data change was unsuccessful due to configuration problems.\n\nCf. e.g. {{msg-mw|Passwordreset-ignored}}.", + "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 589144c..6ae2db8 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -426,6 +426,7 @@ $specialPageAliases = [ 'Fewestrevisions' => [ 'FewestRevisions' ], 'FileDuplicateSearch' => [ 'FileDuplicateSearch' ], 'Filepath' => [ 'FilePath' ], + 'GoToInterwiki' => [ 'GoToInterwiki' ], 'Import' => [ 'Import' ], 'Invalidateemail' => [ 'InvalidateEmail' ], 'JavaScriptTest' => [ 'JavaScriptTest' ], -- 2.8.1.windows.1