CVE-2026-45294
Description
FreeScout is a free help desk and shared inbox built with PHP's Laravel framework. Prior to 1.8.219, the password reset endpoint returns visually distinct responses depending on whether the submitted email address belongs to an existing user account, allowing unauthenticated attackers to enumerate valid helpdesk agent email addresses. This vulnerability is fixed in 1.8.219.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
FreeScout <1.8.219 discloses agent email existence via distinct password reset responses, enabling unauthenticated enumeration.
Vulnerability
In FreeScout versions prior to 1.8.219, the password reset endpoint responds differently based on whether the submitted email address is registered. An existing user yields a div with CSS class alert-success and a success message, while a non‑existing email yields a div with class form-group has-error and the text "We can't find a user with that email address." This distinction allows unauthenticated attackers to determine which email addresses belong to active helpdesk agents. [1]
Exploitation
An attacker sends a password reset request for any email address and inspects the HTML response. The difference in CSS classes and message text can be trivially automated with a script. No authentication, rate limiting, or user interaction is required. The vulnerability is present on version 1.8.218 and earlier commits. [1]
Impact
A remote, unauthenticated attacker can enumerate valid helpdesk agent email addresses. This harvested list can be used for targeted phishing, credential stuffing, or as a stepping stone for further attacks, such as agent impersonation. [1]
Mitigation
The issue is fixed in FreeScout version 1.8.219, which returns an identical response for both existing and non‑existing emails and adds a rate limit to the password reset endpoint. Users should upgrade to 1.8.219 or later. No workaround is available. [1]
AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1369eb9217a7bAdd throttle to the Forgot Password form and return identical response regardless of whether the email exists - GHSA-jvmv-2qcp-7855
2 files changed · +83 −1
app/Http/Controllers/Auth/ForgotPasswordController.php+81 −0 modified@@ -4,6 +4,11 @@ use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; +use Illuminate\Cache\RateLimiter; +use Illuminate\Validation\ValidationException; +use Illuminate\Auth\Events\Lockout; class ForgotPasswordController extends Controller { @@ -20,6 +25,12 @@ class ForgotPasswordController extends Controller use SendsPasswordResetEmails; + // Max reset password attempts per period. + const THROTTLE_ATTEMPTS = 5; + + // Minutes + const THROTTLE_PERIOD = 1; + /** * Create a new controller instance. * @@ -29,4 +40,74 @@ public function __construct() { $this->middleware('guest'); } + + /** + * Send a reset link to the given user. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse + */ + public function sendResetLinkEmail(Request $request) + { + $this->validateEmail($request); + + // https://github.com/freescout-help-desk/freescout/security/advisories/GHSA-jvmv-2qcp-7855 + if ($this->hasTooManyResetEmailAttempts($request)) { + event(new Lockout($request)); + return $this->sendLockoutResponse($request); + } + + // We will send the password reset link to this user. Once we have attempted + // to send the link, we will examine the response then see the message we + // need to show to the user. Finally, we'll send out a proper response. + $response = $this->broker()->sendResetLink( + $request->only('email') + ); + + if ($response !== Password::RESET_LINK_SENT) { + $this->incrementResetEmailAttempts($request); + //return $this->sendResetLinkFailedResponse($request, $response); + } + + $this->clearResetEmailAttempts($request); + + // For security purposes always return an identical response regardless of whether the email exists. + // return $response == Password::RESET_LINK_SENT + // ? $this->sendResetLinkResponse($response) + // : $this->sendResetLinkFailedResponse($request, $response); + return $this->sendResetLinkResponse(__('If an account exists for this email, you will receive a password reset link.')); + } + + protected function hasTooManyResetEmailAttempts(Request $request) + { + return app(RateLimiter::class)->tooManyAttempts( + $this->throttleKey($request), + self::THROTTLE_ATTEMPTS + ); + } + + protected function incrementResetEmailAttempts(Request $request) + { + app(RateLimiter::class)->hit( + $this->throttleKey($request), + self::THROTTLE_PERIOD + ); + } + + protected function clearResetEmailAttempts(Request $request) + { + app(RateLimiter::class)->clear($this->throttleKey($request)); + } + + protected function throttleKey(Request $request) + { + return strtolower($request->ip()) . '|reset_password'; + } + + protected function sendLockoutResponse(Request $request) + { + throw ValidationException::withMessages([ + 'email' => [trans('auth.throttle', ['seconds' => self::THROTTLE_PERIOD*60])], + ])->status(423); + } }
config/auth.php+2 −1 modified@@ -96,7 +96,8 @@ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, - 'throttle' => 60, + // Has no effect. + //'throttle' => 60, ], ],
Vulnerability mechanics
Root cause
"The password reset endpoint returned visually distinct HTML responses for existing vs. non-existing email addresses and lacked rate limiting."
Attack vector
An unauthenticated attacker sends password-reset POST requests to the forgot-password endpoint with candidate email addresses. For existing accounts the response contains `<div class="alert alert-success">`, while for non-existing accounts it contains `<div class="form-group has-error">` with the text "We can't find a user with that email address" [ref_id=1]. No rate limit was applied, allowing rapid automated enumeration of valid helpdesk agent email addresses [CWE-203].
Affected code
The vulnerability exists in `app/Http/Controllers/Auth/ForgotPasswordController.php` (the `sendResetLinkEmail` method) prior to commit `369eb9217a7bd42bad36e12601e864b424d9812d`. The endpoint returned distinct HTML responses depending on whether the submitted email belonged to an existing user, and lacked any rate limiting.
What the fix does
The patch overrides the `sendResetLinkEmail` method to always return the same generic success message regardless of whether the email exists, eliminating the observable difference [patch_id=3107124]. It also adds a rate limiter (5 attempts per minute per IP) that throws a `ValidationException` with a 423 status when exceeded, preventing brute-force enumeration.
Preconditions
- authNo authentication required
- networkAttacker must be able to send HTTP POST requests to the forgot-password endpoint
- configNo special configuration required
Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.