diff -u mediawiki-1.11.2/debian/patches/series mediawiki-1.11.2/debian/patches/series --- mediawiki-1.11.2/debian/patches/series +++ mediawiki-1.11.2/debian/patches/series @@ -8,0 +9 @@ +CSRF-no-CVE_rev-64680.patch diff -u mediawiki-1.11.2/debian/changelog mediawiki-1.11.2/debian/changelog --- mediawiki-1.11.2/debian/changelog +++ mediawiki-1.11.2/debian/changelog @@ -1,3 +1,17 @@ +mediawiki (1:1.11.2-2ubuntu0.5) hardy-security; urgency=low + + * SECURITY UPDATE: MediaWiki was found to be vulnerable to login CSRF. An + attacker who controls a user account on the target wiki can force the + victim to login as the attacker, via a script on an external website. + IMPORTANT: Fix includes a breaking change to the API login action. Any + clients using it will need to be updated. (LP: #557159) + - debian/patches/CSRF-no-CVE_rev-64680.patch + - patch based on upstream SVN rev. 64680 + - http://lists.wikimedia.org/pipermail/mediawiki-announce/2010-April/000090.html + - https://bugzilla.wikimedia.org/show_bug.cgi?id=23076 + + -- Andreas Wenning Wed, 07 Apr 2010 12:08:55 +0200 + mediawiki (1:1.11.2-2ubuntu0.4) hardy-security; urgency=low * SECURITY UPDATE: CSS validation issue allowing external images to be included only in patch2: unchanged: --- mediawiki-1.11.2.orig/debian/patches/CSRF-no-CVE_rev-64680.patch +++ mediawiki-1.11.2/debian/patches/CSRF-no-CVE_rev-64680.patch @@ -0,0 +1,244 @@ +Subject: Fixes login CSRF vulnerability. Fix includes a breaking change to the +API login action. Any clients using it will need to be updated. +Origin: http://svn.wikimedia.org/viewvc/mediawiki?view=rev&revision=64680 +Index: b/includes/User.php +=================================================================== +--- a/includes/User.php 2008-03-03 08:09:26.000000000 +0100 ++++ b/includes/User.php 2010-04-07 11:34:44.000000000 +0200 +@@ -2293,7 +2293,7 @@ + return EDIT_TOKEN_SUFFIX; + } else { + if( !isset( $_SESSION['wsEditToken'] ) ) { +- $token = $this->generateToken(); ++ $token = self::generateToken(); + $_SESSION['wsEditToken'] = $token; + } else { + $token = $_SESSION['wsEditToken']; +@@ -2310,7 +2310,7 @@ + * Could be made more cryptographically sure if someone cares. + * @return string + */ +- function generateToken( $salt = '' ) { ++ public static function generateToken( $salt = '' ) { + $token = dechex( mt_rand() ) . dechex( mt_rand() ); + return md5( $token . $salt ); + } +@@ -2399,7 +2399,7 @@ + $expires = $now + 7 * 24 * 60 * 60; + $expiration = wfTimestamp( TS_MW, $expires ); + +- $token = $this->generateToken( $this->mId . $this->mEmail . $expires ); ++ $token = self::generateToken( $this->mId . $this->mEmail . $expires ); + $hash = md5( $token ); + + $dbw = wfGetDB( DB_MASTER ); +Index: b/includes/api/ApiLogin.php +=================================================================== +--- a/includes/api/ApiLogin.php 2008-03-03 08:09:24.000000000 +0100 ++++ b/includes/api/ApiLogin.php 2010-04-07 11:34:44.000000000 +0200 +@@ -88,6 +88,7 @@ + 'wpName' => $name, + 'wpPassword' => $password, + 'wpDomain' => $domain, ++ 'wpLoginToken' => $token, + 'wpRemember' => '' + )); + +@@ -104,6 +105,15 @@ + $result['lgusername'] = $_SESSION['wsUserName']; + $result['lgtoken'] = $_SESSION['wsToken']; + break; ++ ++ case LoginForm::NEED_TOKEN: ++ $result['result'] = 'NeedToken'; ++ $result['token'] = $loginForm->getLoginToken(); ++ break; ++ ++ case LoginForm::WRONG_TOKEN: ++ $result['result'] = 'WrongToken'; ++ break; + + case LoginForm :: NO_NAME : + $result['result'] = 'NoName'; +@@ -214,7 +224,8 @@ + return array ( + 'name' => null, + 'password' => null, +- 'domain' => null ++ 'domain' => null, ++ 'token' => null, + ); + } + +@@ -222,7 +233,8 @@ + return array ( + 'name' => 'User Name', + 'password' => 'Password', +- 'domain' => 'Domain (optional)' ++ 'domain' => 'Domain (optional)', ++ 'token' => 'Login token obtained in first request', + ); + } + +Index: b/includes/SpecialUserlogin.php +=================================================================== +--- a/includes/SpecialUserlogin.php 2008-03-03 08:09:26.000000000 +0100 ++++ b/includes/SpecialUserlogin.php 2010-04-07 11:34:44.000000000 +0200 +@@ -32,10 +32,14 @@ + const EMPTY_PASS = 6; + const RESET_PASS = 7; + const ABORTED = 8; ++ const USER_BLOCKED = 11; ++ const NEED_TOKEN = 12; ++ const WRONG_TOKEN = 13; + + var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; + var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; + var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; ++ var $mToken; + + /** + * Constructor +@@ -62,6 +66,7 @@ + $this->mAction = $request->getVal( 'action' ); + $this->mRemember = $request->getCheck( 'wpRemember' ); + $this->mLanguage = $request->getText( 'uselang' ); ++ $this->mToken = $request->getVal( 'wpLoginToken' ); + + if( $wgEnableEmail ) { + $this->mEmail = $request->getText( 'wpEmail' ); +@@ -346,6 +351,22 @@ + if ( '' == $this->mName ) { + return self::NO_NAME; + } ++ ++ // We require a login token to prevent login CSRF ++ // Handle part of this before incrementing the throttle so ++ // token-less login attempts don't count towards the throttle ++ // but wrong-token attempts do. ++ ++ // If the user doesn't have a login token yet, set one. ++ if ( !self::getLoginToken() ) { ++ self::setLoginToken(); ++ return self::NEED_TOKEN; ++ } ++ // If the user didn't pass a login token, tell them we need one ++ if ( !$this->mToken ) { ++ return self::NEED_TOKEN; ++ } ++ + $u = User::newFromName( $this->mName ); + if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) { + return self::ILLEGAL; +@@ -369,6 +390,11 @@ + } else { + $u->load(); + } ++ ++ // Validate the login token ++ if ( $this->mToken !== self::getLoginToken() ) { ++ return self::WRONG_TOKEN; ++ } + + // Give general extensions, such as a captcha, a chance to abort logins + $abort = self::ABORTED; +@@ -433,6 +459,7 @@ + $wgUser->invalidateCache(); + } + $wgUser->setCookies(); ++ self::clearLoginToken(); + + if( $this->hasSessionCookie() ) { + return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) ); +@@ -440,7 +467,11 @@ + return $this->cookieRedirectCheck( 'login' ); + } + break; +- ++ ++ case self::NEED_TOKEN: ++ case self::WRONG_TOKEN: ++ $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); ++ break; + case self::NO_NAME: + case self::ILLEGAL: + $this->mainLoginForm( wfMsg( 'noname' ) ); +@@ -692,6 +723,11 @@ + $template->set( 'canreset', $wgAuth->allowPasswordChange() ); + $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); + ++ if ( !self::getLoginToken() ) { ++ self::setLoginToken(); ++ } ++ $template->set( 'token', self::getLoginToken() ); ++ + # Prepare language selection links as needed + if( $wgLoginLanguageSelector ) { + $template->set( 'languages', $this->makeLanguageSelector() ); +@@ -740,6 +776,32 @@ + global $wgDisableCookieCheck, $wgRequest; + return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); + } ++ ++ /** ++ * Get the login token from the current session ++ */ ++ public static function getLoginToken() { ++ global $wgRequest; ++ return $wgRequest->getSessionData( 'wsLoginToken' ); ++ } ++ ++ /** ++ * Generate a new login token and attach it to the current session ++ */ ++ public static function setLoginToken() { ++ global $wgRequest; ++ // Use User::generateToken() instead of $user->editToken() ++ // because the latter reuses $_SESSION['wsEditToken'] ++ $wgRequest->setSessionData( 'wsLoginToken', User::generateToken() ); ++ } ++ ++ /** ++ * Remove any login token attached to the current session ++ */ ++ public static function clearLoginToken() { ++ global $wgRequest; ++ $wgRequest->setSessionData( 'wsLoginToken', null ); ++ } + + /** + * @private +Index: b/includes/templates/Userlogin.php +=================================================================== +--- a/includes/templates/Userlogin.php 2008-03-03 08:09:26.000000000 +0100 ++++ b/includes/templates/Userlogin.php 2010-04-07 11:34:44.000000000 +0200 +@@ -85,6 +85,7 @@ + + + haveData( 'uselang' ) ) { ?> ++haveData( 'token' ) ) { ?> + + +
msgWiki( 'loginend' ); ?>
+Index: b/includes/WebRequest.php +=================================================================== +--- a/includes/WebRequest.php 2008-03-03 08:09:26.000000000 +0100 ++++ b/includes/WebRequest.php 2010-04-07 11:34:44.000000000 +0200 +@@ -569,6 +569,17 @@ + return $this->_response; + } + ++ /* ++ * Get data from $_SESSION ++ */ ++ function getSessionData( $key ) { ++ if( !isset( $_SESSION[$key] ) ) ++ return null; ++ return $_SESSION[$key]; ++ } ++ function setSessionData( $key, $data ) { ++ $_SESSION[$key] = $data; ++ } + } + + /**