VYPR
Moderate severityNVD Advisory· Published Jan 5, 2024· Updated Jun 3, 2025

Flarum's Logout Route allows open redirects

CVE-2024-21641

Description

Flarum is open source discussion platform software. Prior to version 1.8.5, the Flarum /logout route includes a redirect parameter that allows any third party to redirect users from a (trusted) domain of the Flarum installation to redirect to any link. For logged-in users, the logout must be confirmed. Guests are immediately redirected. This could be used by spammers to redirect to a web address using a trusted domain of a running Flarum installation. The vulnerability has been fixed and published as flarum/core v1.8.5. As a workaround, some extensions modifying the logout route can remedy this issue if their implementation is safe.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
flarum/corePackagist
< 1.8.51.8.5
flarum/frameworkPackagist
< 1.8.51.8.5

Affected products

1

Patches

2
ee8b3b4ad141

[1.x] fix: Logout controller allows open redirects (#3948)

1 file changed · +47 8
  • src/Forum/Controller/LogOutController.php+47 8 modified
    @@ -9,6 +9,7 @@
     
     namespace Flarum\Forum\Controller;
     
    +use Flarum\Foundation\Config;
     use Flarum\Http\Exception\TokenMismatchException;
     use Flarum\Http\Rememberer;
     use Flarum\Http\RequestUtil;
    @@ -20,6 +21,7 @@
     use Illuminate\Support\Arr;
     use Laminas\Diactoros\Response\HtmlResponse;
     use Laminas\Diactoros\Response\RedirectResponse;
    +use Laminas\Diactoros\Uri;
     use Psr\Http\Message\ResponseInterface;
     use Psr\Http\Message\ServerRequestInterface as Request;
     use Psr\Http\Server\RequestHandlerInterface;
    @@ -51,25 +53,33 @@ class LogOutController implements RequestHandlerInterface
          */
         protected $url;
     
    +    /**
    +     * @var Config
    +     */
    +    protected $config;
    +
         /**
          * @param Dispatcher $events
          * @param SessionAuthenticator $authenticator
          * @param Rememberer $rememberer
          * @param Factory $view
          * @param UrlGenerator $url
    +     * @param Config $config
          */
         public function __construct(
             Dispatcher $events,
             SessionAuthenticator $authenticator,
             Rememberer $rememberer,
             Factory $view,
    -        UrlGenerator $url
    +        UrlGenerator $url,
    +        Config $config
         ) {
             $this->events = $events;
             $this->authenticator = $authenticator;
             $this->rememberer = $rememberer;
             $this->view = $view;
             $this->url = $url;
    +        $this->config = $config;
         }
     
         /**
    @@ -81,29 +91,29 @@ public function handle(Request $request): ResponseInterface
         {
             $session = $request->getAttribute('session');
             $actor = RequestUtil::getActor($request);
    +        $base = $this->url->to('forum')->base();
     
    -        $url = Arr::get($request->getQueryParams(), 'return', $this->url->to('forum')->base());
    +        $returnUrl = Arr::get($request->getQueryParams(), 'return');
    +        $return = $this->sanitizeReturnUrl((string) $returnUrl, $base);
     
    -        // If there is no user logged in, return to the index.
    +        // If there is no user logged in, return to the index or the return url if it's set.
             if ($actor->isGuest()) {
    -            return new RedirectResponse($url);
    +            return new RedirectResponse($return);
             }
     
             // If a valid CSRF token hasn't been provided, show a view which will
             // allow the user to press a button to complete the log out process.
             $csrfToken = $session->token();
     
             if (Arr::get($request->getQueryParams(), 'token') !== $csrfToken) {
    -            $return = Arr::get($request->getQueryParams(), 'return');
    -
                 $view = $this->view->make('flarum.forum::log-out')
    -                ->with('url', $this->url->to('forum')->route('logout').'?token='.$csrfToken.($return ? '&return='.urlencode($return) : ''));
    +                ->with('url', $this->url->to('forum')->route('logout') . '?token=' . $csrfToken . ($returnUrl ? '&return=' . urlencode($return) : ''));
     
                 return new HtmlResponse($view->render());
             }
     
             $accessToken = $session->get('access_token');
    -        $response = new RedirectResponse($url);
    +        $response = new RedirectResponse($return);
     
             $this->authenticator->logOut($session);
     
    @@ -113,4 +123,33 @@ public function handle(Request $request): ResponseInterface
     
             return $this->rememberer->forget($response);
         }
    +
    +    protected function sanitizeReturnUrl(string $url, string $base): Uri
    +    {
    +        if (empty($url)) {
    +            return new Uri($base);
    +        }
    +
    +        try {
    +            $parsedUrl = new Uri($url);
    +        } catch (\InvalidArgumentException $e) {
    +            return new Uri($base);
    +        }
    +
    +        if (in_array($parsedUrl->getHost(), $this->getAllowedRedirectDomains())) {
    +            return $parsedUrl;
    +        }
    +
    +        return new Uri($base);
    +    }
    +
    +    protected function getAllowedRedirectDomains(): array
    +    {
    +        $forumUri = $this->config->url();
    +
    +        return array_merge(
    +            [$forumUri->getHost()],
    +            $this->config->offsetGet('redirectDomains') ?? []
    +        );
    +    }
     }
    
7d70328471cf

[1.x] fix: Logout controller allows open redirects (#3948)

1 file changed · +47 8
  • framework/core/src/Forum/Controller/LogOutController.php+47 8 modified
    @@ -9,6 +9,7 @@
     
     namespace Flarum\Forum\Controller;
     
    +use Flarum\Foundation\Config;
     use Flarum\Http\Exception\TokenMismatchException;
     use Flarum\Http\Rememberer;
     use Flarum\Http\RequestUtil;
    @@ -20,6 +21,7 @@
     use Illuminate\Support\Arr;
     use Laminas\Diactoros\Response\HtmlResponse;
     use Laminas\Diactoros\Response\RedirectResponse;
    +use Laminas\Diactoros\Uri;
     use Psr\Http\Message\ResponseInterface;
     use Psr\Http\Message\ServerRequestInterface as Request;
     use Psr\Http\Server\RequestHandlerInterface;
    @@ -51,25 +53,33 @@ class LogOutController implements RequestHandlerInterface
          */
         protected $url;
     
    +    /**
    +     * @var Config
    +     */
    +    protected $config;
    +
         /**
          * @param Dispatcher $events
          * @param SessionAuthenticator $authenticator
          * @param Rememberer $rememberer
          * @param Factory $view
          * @param UrlGenerator $url
    +     * @param Config $config
          */
         public function __construct(
             Dispatcher $events,
             SessionAuthenticator $authenticator,
             Rememberer $rememberer,
             Factory $view,
    -        UrlGenerator $url
    +        UrlGenerator $url,
    +        Config $config
         ) {
             $this->events = $events;
             $this->authenticator = $authenticator;
             $this->rememberer = $rememberer;
             $this->view = $view;
             $this->url = $url;
    +        $this->config = $config;
         }
     
         /**
    @@ -81,29 +91,29 @@ public function handle(Request $request): ResponseInterface
         {
             $session = $request->getAttribute('session');
             $actor = RequestUtil::getActor($request);
    +        $base = $this->url->to('forum')->base();
     
    -        $url = Arr::get($request->getQueryParams(), 'return', $this->url->to('forum')->base());
    +        $returnUrl = Arr::get($request->getQueryParams(), 'return');
    +        $return = $this->sanitizeReturnUrl((string) $returnUrl, $base);
     
    -        // If there is no user logged in, return to the index.
    +        // If there is no user logged in, return to the index or the return url if it's set.
             if ($actor->isGuest()) {
    -            return new RedirectResponse($url);
    +            return new RedirectResponse($return);
             }
     
             // If a valid CSRF token hasn't been provided, show a view which will
             // allow the user to press a button to complete the log out process.
             $csrfToken = $session->token();
     
             if (Arr::get($request->getQueryParams(), 'token') !== $csrfToken) {
    -            $return = Arr::get($request->getQueryParams(), 'return');
    -
                 $view = $this->view->make('flarum.forum::log-out')
    -                ->with('url', $this->url->to('forum')->route('logout').'?token='.$csrfToken.($return ? '&return='.urlencode($return) : ''));
    +                ->with('url', $this->url->to('forum')->route('logout') . '?token=' . $csrfToken . ($returnUrl ? '&return=' . urlencode($return) : ''));
     
                 return new HtmlResponse($view->render());
             }
     
             $accessToken = $session->get('access_token');
    -        $response = new RedirectResponse($url);
    +        $response = new RedirectResponse($return);
     
             $this->authenticator->logOut($session);
     
    @@ -113,4 +123,33 @@ public function handle(Request $request): ResponseInterface
     
             return $this->rememberer->forget($response);
         }
    +
    +    protected function sanitizeReturnUrl(string $url, string $base): Uri
    +    {
    +        if (empty($url)) {
    +            return new Uri($base);
    +        }
    +
    +        try {
    +            $parsedUrl = new Uri($url);
    +        } catch (\InvalidArgumentException $e) {
    +            return new Uri($base);
    +        }
    +
    +        if (in_array($parsedUrl->getHost(), $this->getAllowedRedirectDomains())) {
    +            return $parsedUrl;
    +        }
    +
    +        return new Uri($base);
    +    }
    +
    +    protected function getAllowedRedirectDomains(): array
    +    {
    +        $forumUri = $this->config->url();
    +
    +        return array_merge(
    +            [$forumUri->getHost()],
    +            $this->config->offsetGet('redirectDomains') ?? []
    +        );
    +    }
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.