Preferences: Updated return redirect with better origin checks

As suggested by Alex Dan in their security report.
This commit is contained in:
Dan Brown
2026-03-10 18:31:51 +00:00
parent 6216c89f82
commit 6e7cc169d1
2 changed files with 34 additions and 2 deletions

View File

@@ -167,14 +167,26 @@ abstract class Controller extends BaseController
/**
* Redirect to the URL provided in the request as a '_return' parameter.
* Will check that the parameter leads to a URL under the root path of the system.
* Will check that the parameter leads to a URL under the same origin as the application.
*/
protected function redirectToRequest(Request $request): RedirectResponse
{
$basePath = url('/');
$returnUrl = $request->input('_return') ?? $basePath;
if (!str_starts_with($returnUrl, $basePath)) {
// Only allow use of _return on requests where we expect CSRF to be active
// to prevent it potentially being used as an open redirect
$allowedMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
if (!in_array($request->getMethod(), $allowedMethods)) {
return redirect($basePath);
}
$intendedUrl = parse_url($returnUrl);
$baseUrl = parse_url($basePath);
$isSameOrigin = ($intendedUrl['host'] ?? '') === ($baseUrl['host'] ?? '')
&& ($intendedUrl['scheme'] ?? '') === ($baseUrl['scheme'] ?? '')
&& ($intendedUrl['port'] ?? 0) === ($baseUrl['port'] ?? 0);
if (!$isSameOrigin) {
return redirect($basePath);
}

View File

@@ -153,6 +153,26 @@ class UserPreferencesTest extends TestCase
->assertElementNotExists('.content-wrap .entity-list-item');
}
public function test_redirect_on_preference_change_checks_host()
{
$expectedByRedirect = [
'http://localhost/beans' => 'http://localhost/beans',
'https://localhost/beans' => 'http://localhost',
'http://localhost:9090/beans' => 'http://localhost',
'http://localhost.example.com/beans' => 'http://localhost',
'http://localhost@example.com/beans' => 'http://localhost',
];
$this->asEditor();
foreach ($expectedByRedirect as $url => $expected) {
$req = $this->patch("/preferences/change-view/bookshelf", [
'view' => 'grid',
'_return' => $url,
]);
$req->assertRedirect($expected);
}
}
public function test_update_code_language_favourite()
{
$editor = $this->users->editor();