VYPR
Medium severity5.3NVD Advisory· Published May 29, 2026· Updated May 29, 2026

CVE-2026-45294

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

2
  • Freescout/Freescoutinferred2 versions
    <1.8.219+ 1 more
    • (no CPE)range: <1.8.219
    • (no CPE)range: <1.8.219

Patches

1
369eb9217a7b

Add throttle to the Forgot Password form and return identical response regardless of whether the email exists - GHSA-jvmv-2qcp-7855

https://github.com/freescout-help-desk/freescoutFreeScoutMay 3, 2026Fixed in 1.8.219via llm-release-walk
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

1

News mentions

0

No linked articles in our index yet.