firstOrFail(); session(['active_server_id' => $server->id]); $this->redirect($this->safeReturnUrl(), navigate: true); } /** * Return a SAME-ORIGIN relative target derived from the Referer, or the * dashboard. An attacker-supplied Referer must never turn a server switch * into an open redirect, so we: * 1. reject any Referer whose host differs or whose scheme is not http(s) * (blocks javascript:/data: and off-site hosts), then * 2. keep ONLY the path + query and redirect to that rooted relative URL — * so the result can never carry a foreign scheme, host, or port (e.g. * `https://host:444/x` collapses to `/x` on the current origin), and * 3. reject protocol-relative (`//host`, `/\host`) and non-rooted values. */ private function safeReturnUrl(): string { $referer = request()->header('Referer'); $fallback = route('dashboard'); if (! is_string($referer) || $referer === '') { return $fallback; } $parts = parse_url($referer); if ($parts === false || ! isset($parts['host']) || strtolower($parts['host']) !== strtolower(request()->getHost()) || (isset($parts['scheme']) && ! in_array(strtolower($parts['scheme']), ['http', 'https'], true))) { return $fallback; } $target = ($parts['path'] ?? '/').(isset($parts['query']) ? '?'.$parts['query'] : ''); // Must be a rooted, host-relative path; reject protocol-relative tricks. if ($target === '' || $target[0] !== '/' || (isset($target[1]) && ($target[1] === '/' || $target[1] === '\\'))) { return $fallback; } return $target; } public function render() { $servers = Server::orderBy('name')->get(); $active = $servers->firstWhere('id', session('active_server_id')) ?? $servers->firstWhere('status', 'online') ?? $servers->first(); return view('livewire.server-switcher', [ 'servers' => $servers, 'active' => $active, ]); } }