From 4ee667dcfbb99ebdc4f06a65bfbac29ffaa04100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Richiardi?= <142845401+KroderDev@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:31:40 -0400 Subject: [PATCH] Validate redirect parameter domains --- CHANGELOG.md | 3 +++ src/Traits/RedirectsIfRequested.php | 22 +++++++++++++++++++--- src/config/microservice.php | 3 +++ tests/Http/GatewayAuthControllersTest.php | 18 ++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 557348a..e13771b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Prevent external redirects by validating `redirect` parameters against an allow list. + ## [0.4.3] - 2025-08-13 ### Fixed - `paginate()` returns an empty paginator with a 200 status when the gateway responds with 404. diff --git a/src/Traits/RedirectsIfRequested.php b/src/Traits/RedirectsIfRequested.php index 967fda8..cc803fb 100644 --- a/src/Traits/RedirectsIfRequested.php +++ b/src/Traits/RedirectsIfRequested.php @@ -13,10 +13,13 @@ protected function redirectIfRequested(Request $request, $response) { if ($request->has('redirect')) { $redirectTo = $request->input('redirect'); + if ($redirectTo === 'intended') { + return redirect()->intended(); + } - return $redirectTo === 'intended' - ? redirect()->intended() - : redirect()->to($redirectTo); + if ($this->isAllowedRedirect($redirectTo)) { + return redirect()->to($redirectTo); + } } if (Session::has('url.intended')) { @@ -31,4 +34,17 @@ protected function redirectIfRequested(Request $request, $response) return $response; } + + protected function isAllowedRedirect(string $url): bool + { + $host = parse_url($url, PHP_URL_HOST); + + if (! $host) { + return str_starts_with($url, '/'); + } + + $allowed = Config::get('microservice.gateway_auth.allowed_redirect_hosts', []); + + return in_array($host, $allowed, true); + } } diff --git a/src/config/microservice.php b/src/config/microservice.php index 1b06aea..33fb807 100644 --- a/src/config/microservice.php +++ b/src/config/microservice.php @@ -166,6 +166,9 @@ */ 'gateway_auth' => [ 'default_redirect' => env('GATEWAY_AUTH_DEFAULT_REDIRECT', '/'), + 'allowed_redirect_hosts' => array_filter( + explode(',', env('GATEWAY_AUTH_ALLOWED_REDIRECT_HOSTS', '')) + ), ], /* diff --git a/tests/Http/GatewayAuthControllersTest.php b/tests/Http/GatewayAuthControllersTest.php index f18a1ef..9e14892 100644 --- a/tests/Http/GatewayAuthControllersTest.php +++ b/tests/Http/GatewayAuthControllersTest.php @@ -143,6 +143,24 @@ public function login_controller_redirects_to_configured_default_when_no_json() ->assertRedirect('/default'); } + /** @test */ + public function login_controller_blocks_external_redirects() + { + Config::set('microservice.gateway_auth.default_redirect', '/default'); + + $this->post('/login?redirect=https://evil.test', ['email' => 'foo', 'password' => 'bar']) + ->assertRedirect('/default'); + } + + /** @test */ + public function login_controller_allows_redirects_to_configured_domains() + { + Config::set('microservice.gateway_auth.allowed_redirect_hosts', ['good.test']); + + $this->post('/login?redirect=https://good.test/welcome', ['email' => 'foo', 'password' => 'bar']) + ->assertRedirect('https://good.test/welcome'); + } + /** @test */ public function register_controller_redirects_if_requested() {