Skip to content

Commit 7d70328

Browse files
authored
[1.x] fix: Logout controller allows open redirects (#3948)
* fix: prevent open redirects on logout controller * use clearer config key * cast url as string, reinstate guest redirect * clean up a little * simplify * return Uri * resolve ternary always true * simplify some more * remove extra newline * handle malformed uri * chore: requested changes
1 parent 45a8b57 commit 7d70328

File tree

1 file changed

+47
-8
lines changed

1 file changed

+47
-8
lines changed

framework/core/src/Forum/Controller/LogOutController.php

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace Flarum\Forum\Controller;
1111

12+
use Flarum\Foundation\Config;
1213
use Flarum\Http\Exception\TokenMismatchException;
1314
use Flarum\Http\Rememberer;
1415
use Flarum\Http\RequestUtil;
@@ -20,6 +21,7 @@
2021
use Illuminate\Support\Arr;
2122
use Laminas\Diactoros\Response\HtmlResponse;
2223
use Laminas\Diactoros\Response\RedirectResponse;
24+
use Laminas\Diactoros\Uri;
2325
use Psr\Http\Message\ResponseInterface;
2426
use Psr\Http\Message\ServerRequestInterface as Request;
2527
use Psr\Http\Server\RequestHandlerInterface;
@@ -51,25 +53,33 @@ class LogOutController implements RequestHandlerInterface
5153
*/
5254
protected $url;
5355

56+
/**
57+
* @var Config
58+
*/
59+
protected $config;
60+
5461
/**
5562
* @param Dispatcher $events
5663
* @param SessionAuthenticator $authenticator
5764
* @param Rememberer $rememberer
5865
* @param Factory $view
5966
* @param UrlGenerator $url
67+
* @param Config $config
6068
*/
6169
public function __construct(
6270
Dispatcher $events,
6371
SessionAuthenticator $authenticator,
6472
Rememberer $rememberer,
6573
Factory $view,
66-
UrlGenerator $url
74+
UrlGenerator $url,
75+
Config $config
6776
) {
6877
$this->events = $events;
6978
$this->authenticator = $authenticator;
7079
$this->rememberer = $rememberer;
7180
$this->view = $view;
7281
$this->url = $url;
82+
$this->config = $config;
7383
}
7484

7585
/**
@@ -81,29 +91,29 @@ public function handle(Request $request): ResponseInterface
8191
{
8292
$session = $request->getAttribute('session');
8393
$actor = RequestUtil::getActor($request);
94+
$base = $this->url->to('forum')->base();
8495

85-
$url = Arr::get($request->getQueryParams(), 'return', $this->url->to('forum')->base());
96+
$returnUrl = Arr::get($request->getQueryParams(), 'return');
97+
$return = $this->sanitizeReturnUrl((string) $returnUrl, $base);
8698

87-
// If there is no user logged in, return to the index.
99+
// If there is no user logged in, return to the index or the return url if it's set.
88100
if ($actor->isGuest()) {
89-
return new RedirectResponse($url);
101+
return new RedirectResponse($return);
90102
}
91103

92104
// If a valid CSRF token hasn't been provided, show a view which will
93105
// allow the user to press a button to complete the log out process.
94106
$csrfToken = $session->token();
95107

96108
if (Arr::get($request->getQueryParams(), 'token') !== $csrfToken) {
97-
$return = Arr::get($request->getQueryParams(), 'return');
98-
99109
$view = $this->view->make('flarum.forum::log-out')
100-
->with('url', $this->url->to('forum')->route('logout').'?token='.$csrfToken.($return ? '&return='.urlencode($return) : ''));
110+
->with('url', $this->url->to('forum')->route('logout') . '?token=' . $csrfToken . ($returnUrl ? '&return=' . urlencode($return) : ''));
101111

102112
return new HtmlResponse($view->render());
103113
}
104114

105115
$accessToken = $session->get('access_token');
106-
$response = new RedirectResponse($url);
116+
$response = new RedirectResponse($return);
107117

108118
$this->authenticator->logOut($session);
109119

@@ -113,4 +123,33 @@ public function handle(Request $request): ResponseInterface
113123

114124
return $this->rememberer->forget($response);
115125
}
126+
127+
protected function sanitizeReturnUrl(string $url, string $base): Uri
128+
{
129+
if (empty($url)) {
130+
return new Uri($base);
131+
}
132+
133+
try {
134+
$parsedUrl = new Uri($url);
135+
} catch (\InvalidArgumentException $e) {
136+
return new Uri($base);
137+
}
138+
139+
if (in_array($parsedUrl->getHost(), $this->getAllowedRedirectDomains())) {
140+
return $parsedUrl;
141+
}
142+
143+
return new Uri($base);
144+
}
145+
146+
protected function getAllowedRedirectDomains(): array
147+
{
148+
$forumUri = $this->config->url();
149+
150+
return array_merge(
151+
[$forumUri->getHost()],
152+
$this->config->offsetGet('redirectDomains') ?? []
153+
);
154+
}
116155
}

0 commit comments

Comments
 (0)