Index: branches/REL1_16/phase3/includes/specials/SpecialUserlogin.php |
— | — | @@ -72,7 +72,7 @@ |
73 | 73 | $this->mRemember = $request->getCheck( 'wpRemember' ); |
74 | 74 | $this->mLanguage = $request->getText( 'uselang' ); |
75 | 75 | $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); |
76 | | - $this->mToken = $request->getVal( 'wpLoginToken' ); |
| 76 | + $this->mToken = ($this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' ); |
77 | 77 | |
78 | 78 | if ( $wgRedirectOnLogin ) { |
79 | 79 | $this->mReturnTo = $wgRedirectOnLogin; |
— | — | @@ -251,6 +251,25 @@ |
252 | 252 | return false; |
253 | 253 | } |
254 | 254 | |
| 255 | + # Request forgery checks. |
| 256 | + if ( !self::getCreateaccountToken() ) { |
| 257 | + self::setCreateaccountToken(); |
| 258 | + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
| 259 | + return false; |
| 260 | + } |
| 261 | + |
| 262 | + # The user didn't pass a createaccount token |
| 263 | + if ( !$this->mToken ) { |
| 264 | + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
| 265 | + return false; |
| 266 | + } |
| 267 | + |
| 268 | + # Validate the createaccount token |
| 269 | + if ( $this->mToken !== self::getCreateaccountToken() ) { |
| 270 | + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
| 271 | + return false; |
| 272 | + } |
| 273 | + |
255 | 274 | # Check permissions |
256 | 275 | if ( !$wgUser->isAllowed( 'createaccount' ) ) { |
257 | 276 | $this->userNotPrivilegedMessage(); |
— | — | @@ -263,7 +282,7 @@ |
264 | 283 | $ip = wfGetIP(); |
265 | 284 | if ( $wgUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) { |
266 | 285 | $this->mainLoginForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' ); |
267 | | - return; |
| 286 | + return false; |
268 | 287 | } |
269 | 288 | |
270 | 289 | # Now create a dummy user ($u) and check if it is valid |
— | — | @@ -340,6 +359,7 @@ |
341 | 360 | return false; |
342 | 361 | } |
343 | 362 | |
| 363 | + self::clearCreateaccountToken(); |
344 | 364 | return $this->initUser( $u, false ); |
345 | 365 | } |
346 | 366 | |
— | — | @@ -683,20 +703,33 @@ |
684 | 704 | return; |
685 | 705 | } |
686 | 706 | |
687 | | - # Check against blocked IPs |
688 | | - # fixme -- should we not? |
| 707 | + # Check against blocked IPs so blocked users can't flood admins |
| 708 | + # with password resets |
689 | 709 | if( $wgUser->isBlocked() ) { |
690 | 710 | $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); |
691 | 711 | return; |
692 | 712 | } |
693 | 713 | |
694 | | - // Check for hooks |
| 714 | + # Check for hooks |
695 | 715 | $error = null; |
696 | 716 | if ( ! wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) { |
697 | 717 | $this->mainLoginForm( $error ); |
698 | 718 | return; |
699 | 719 | } |
700 | 720 | |
| 721 | + # If the user doesn't have a login token yet, set one. |
| 722 | + if ( !self::getLoginToken() ) { |
| 723 | + self::setLoginToken(); |
| 724 | + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
| 725 | + return; |
| 726 | + } |
| 727 | + |
| 728 | + # If the user didn't pass a login token, tell them we need one |
| 729 | + if ( !$this->mToken ) { |
| 730 | + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
| 731 | + return; |
| 732 | + } |
| 733 | + |
701 | 734 | # Check against the rate limiter |
702 | 735 | if( $wgUser->pingLimiter( 'mailpassword' ) ) { |
703 | 736 | $wgOut->rateLimited(); |
— | — | @@ -717,6 +750,12 @@ |
718 | 751 | return; |
719 | 752 | } |
720 | 753 | |
| 754 | + # Validate the login token |
| 755 | + if ( $this->mToken !== self::getLoginToken() ) { |
| 756 | + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
| 757 | + return; |
| 758 | + } |
| 759 | + |
721 | 760 | # Check against password throttle |
722 | 761 | if ( $u->isPasswordReminderThrottled() ) { |
723 | 762 | global $wgPasswordReminderResendTime; |
— | — | @@ -732,6 +771,7 @@ |
733 | 772 | $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); |
734 | 773 | } else { |
735 | 774 | $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); |
| 775 | + self::clearLoginToken(); |
736 | 776 | } |
737 | 777 | } |
738 | 778 | |
— | — | @@ -965,11 +1005,18 @@ |
966 | 1006 | $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); |
967 | 1007 | $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); |
968 | 1008 | |
969 | | - if ( !self::getLoginToken() ) { |
970 | | - self::setLoginToken(); |
| 1009 | + if ( $this->mType == 'signup' ) { |
| 1010 | + if ( !self::getCreateaccountToken() ) { |
| 1011 | + self::setCreateaccountToken(); |
| 1012 | + } |
| 1013 | + $template->set( 'token', self::getCreateaccountToken() ); |
| 1014 | + } else { |
| 1015 | + if ( !self::getLoginToken() ) { |
| 1016 | + self::setLoginToken(); |
| 1017 | + } |
| 1018 | + $template->set( 'token', self::getLoginToken() ); |
971 | 1019 | } |
972 | | - $template->set( 'token', self::getLoginToken() ); |
973 | | - |
| 1020 | + |
974 | 1021 | # Prepare language selection links as needed |
975 | 1022 | if( $wgLoginLanguageSelector ) { |
976 | 1023 | $template->set( 'languages', $this->makeLanguageSelector() ); |
— | — | @@ -1034,7 +1081,7 @@ |
1035 | 1082 | } |
1036 | 1083 | |
1037 | 1084 | /** |
1038 | | - * Generate a new login token and attach it to the current session |
| 1085 | + * Randomly generate a new login token and attach it to the current session |
1039 | 1086 | */ |
1040 | 1087 | public static function setLoginToken() { |
1041 | 1088 | global $wgRequest; |
— | — | @@ -1046,12 +1093,36 @@ |
1047 | 1094 | /** |
1048 | 1095 | * Remove any login token attached to the current session |
1049 | 1096 | */ |
1050 | | - public static function clearLoginToken() { |
| 1097 | + public static function clearLoginToken() { |
1051 | 1098 | global $wgRequest; |
1052 | 1099 | $wgRequest->setSessionData( 'wsLoginToken', null ); |
1053 | 1100 | } |
1054 | 1101 | |
1055 | 1102 | /** |
| 1103 | + * Get the createaccount token from the current session |
| 1104 | + */ |
| 1105 | + public static function getCreateaccountToken() { |
| 1106 | + global $wgRequest; |
| 1107 | + return $wgRequest->getSessionData( 'wsCreateaccountToken' ); |
| 1108 | + } |
| 1109 | + |
| 1110 | + /** |
| 1111 | + * Randomly generate a new createaccount token and attach it to the current session |
| 1112 | + */ |
| 1113 | + public static function setCreateaccountToken() { |
| 1114 | + global $wgRequest; |
| 1115 | + $wgRequest->setSessionData( 'wsCreateaccountToken', User::generateToken() ); |
| 1116 | + } |
| 1117 | + |
| 1118 | + /** |
| 1119 | + * Remove any createaccount token attached to the current session |
| 1120 | + */ |
| 1121 | + public static function clearCreateaccountToken() { |
| 1122 | + global $wgRequest; |
| 1123 | + $wgRequest->setSessionData( 'wsCreateaccountToken', null ); |
| 1124 | + } |
| 1125 | + |
| 1126 | + /** |
1056 | 1127 | * @private |
1057 | 1128 | */ |
1058 | 1129 | function cookieRedirectCheck( $type ) { |
Index: branches/REL1_16/phase3/includes/templates/Userlogin.php |
— | — | @@ -315,6 +315,7 @@ |
316 | 316 | </tr> |
317 | 317 | </table> |
318 | 318 | <?php if( @$this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?> |
| 319 | +<?php if( @$this->haveData( 'token' ) ) { ?><input type="hidden" name="wpCreateaccountToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?> |
319 | 320 | </form> |
320 | 321 | </div> |
321 | 322 | <div id="signupend"><?php $this->msgWiki( 'signupend' ); ?></div> |
Index: branches/REL1_16/phase3/RELEASE-NOTES |
— | — | @@ -40,6 +40,9 @@ |
41 | 41 | you have the DBA extension for PHP installed, this will improve performance |
42 | 42 | further. |
43 | 43 | |
| 44 | +* (bug 23371) Fixed holes in Special:Userlogin which could lead to abuse of |
| 45 | +the system, and disclosure of information from private wikis. |
| 46 | + |
44 | 47 | == Changes since 1.16 beta 2 == |
45 | 48 | |
46 | 49 | * Fixed bugs in the [[Special:Userlogin]] and [[Special:Emailuser]] handling of |