From 8f5bd214a49a73edf50041ec35b5e6b97ccef12f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 4 Jul 2018 11:41:56 -0700 Subject: [PATCH] [Security] Address 2FA bypass in password reset functionality Thanks to Trixter#0001 on Discord for this security report. There was a two-factor authentication bypass present in all previous versions of Pterodactyl that would allow a user to login without providing a token by going through the password reset process. A person would still have to have access to the targeted account's email, but if they did manage to get a password reset link they would be able to reset the account password and then proceede to login without a token being required. This logic has since been changed to check if 2FA is enabled on an account, and if so they will NOT be logged in when their password is changed. This will force them to continue through the normal login pathway where a token will be needed. Overall the impact of this issue is minor, but I am still addressing it and disclosing the mechanism behind it. --- .../Auth/ResetPasswordController.php | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 226958416..d1f21994f 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -2,8 +2,14 @@ namespace Pterodactyl\Http\Controllers\Auth; +use Illuminate\Support\Str; +use Prologue\Alerts\AlertsMessageBag; +use Illuminate\Contracts\Hashing\Hasher; +use Illuminate\Auth\Events\PasswordReset; +use Illuminate\Contracts\Events\Dispatcher; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class ResetPasswordController extends Controller { @@ -16,6 +22,47 @@ class ResetPasswordController extends Controller */ public $redirectTo = '/'; + /** + * @var bool + */ + protected $hasTwoFactor = false; + + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + private $alerts; + + /** + * @var \Illuminate\Contracts\Events\Dispatcher + */ + private $dispatcher; + + /** + * @var \Illuminate\Contracts\Hashing\Hasher + */ + private $hasher; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + private $userRepository; + + /** + * ResetPasswordController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alerts + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository + */ + public function __construct(AlertsMessageBag $alerts, Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository) + { + $this->alerts = $alerts; + $this->dispatcher = $dispatcher; + $this->hasher = $hasher; + $this->userRepository = $userRepository; + } + /** * Return the rules used when validating password reset. * @@ -29,4 +76,49 @@ class ResetPasswordController extends Controller 'password' => 'required|confirmed|min:8', ]; } + + /** + * Reset the given user's password. If the user has two-factor authentication enabled on their + * account do not automatically log them in. In those cases, send the user back to the login + * form with a note telling them their password was changed and to log back in. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword|\Pterodactyl\Models\User $user + * @param string $password + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + protected function resetPassword($user, $password) + { + $user = $this->userRepository->update($user->id, [ + 'password' => $this->hasher->make($password), + $user->getRememberTokenName() => Str::random(60), + ]); + + $this->dispatcher->dispatch(new PasswordReset($user)); + + // If the user is not using 2FA log them in, otherwise skip this step and force a + // fresh login where they'll be prompted to enter a token. + if (! $user->use_totp) { + $this->guard()->login($user); + } + + $this->hasTwoFactor = $user->use_totp; + } + + /** + * Get the response for a successful password reset. + * + * @param string $response + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse + */ + protected function sendResetResponse($response) + { + if ($this->hasTwoFactor) { + $this->alerts->success('Your password was successfully updated. Please log in to continue.')->flash(); + } + + return redirect($this->hasTwoFactor ? route('auth.login') : $this->redirectPath()) + ->with('status', trans($response)); + } }