diff --git a/app/Http/Middleware/DangerZone.php b/app/Http/Middleware/DangerZone.php index a57b73ebb..3f6fe36a7 100644 --- a/app/Http/Middleware/DangerZone.php +++ b/app/Http/Middleware/DangerZone.php @@ -26,9 +26,12 @@ class DangerZone } if ($request->session()->get('sudoModeAttempts') > 3) { - $request->session()->pull('redirectNext'); - $request->session()->pull('sudoModeAttempts'); + // Invalidate the whole session so no security-related flags survive + // the forced logout. Pulling only redirectNext/sudoModeAttempts left + // 2fa.session.active (and sudoMode) intact, which allowed the next + // login on the same session to skip the 2FA checkpoint. Auth::logout(); + $request->session()->invalidate(); return redirect(route('login')); } diff --git a/tests/Feature/Security/DangerZoneLogoutSessionTest.php b/tests/Feature/Security/DangerZoneLogoutSessionTest.php new file mode 100644 index 000000000..e1d1baf4d --- /dev/null +++ b/tests/Feature/Security/DangerZoneLogoutSessionTest.php @@ -0,0 +1,48 @@ +create(); + $user->refresh(); + + $request = Request::create('/settings/email', 'GET'); + $session = new Store('test', new ArraySessionHandler(120)); + $request->setLaravelSession($session); + $request->setUserResolver(fn () => $user); + + $session->put('sudoModeAttempts', 4); + $session->put('2fa.session.active', true); + $session->put('sudoMode', now()->timestamp); + + $response = (new DangerZone)->handle($request, function ($req) { + return response('passed-through', 200); + }); + + expect($response->isRedirect())->toBeTrue(); + expect($session->has('2fa.session.active'))->toBeFalse(); + expect($session->has('sudoMode'))->toBeFalse(); + expect($session->has('sudoModeAttempts'))->toBeFalse(); +});