fix: use resolveSafeRedirect() in Login POST handler to prevent Open Redirect (CWE-601)#3658
fix: use resolveSafeRedirect() in Login POST handler to prevent Open Redirect (CWE-601)#3658javokhir-sec wants to merge 3 commits into
Conversation
f33ad6b to
d9b3737
Compare
…redirect The Login POST handler read redirectUrl directly with only FILTER_SANITIZE_URL and urldecode(), neither of which validates the redirect hostname. An attacker could craft a login form with a redirectUrl pointing to a phishing site. The GET handler already used resolveSafeRedirect() which validates that the redirect target is an internal URL. This fix aligns the POST handler with the same safe pattern. CWE-601 (URL Redirection to Untrusted Site) CVSS:3.1 AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N (6.1) Co-Authored-By: Claude <noreply@anthropic.com>
d9b3737 to
5d0cafc
Compare
There was a problem hiding this comment.
Pull request overview
This PR addresses an Open Redirect (CWE-601) risk in the Auth login flow by routing the post-login redirect through the existing AuthService::resolveSafeRedirect() helper, defaulting to the dashboard when the target is invalid.
Changes:
- Updated the Login POST handler to validate the user-supplied redirect target via
resolveSafeRedirect(). - Removed inline sanitization / fallback logic in
Login::post()in favor of the shared helper.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $redirectUrl = BASE_URL.'/dashboard/home'; | ||
| } | ||
|
|
||
| $redirectUrl = $this->authService->resolveSafeRedirect($_POST['redirectUrl'] ?? null); |
|
Status: ready-pending-CI · Priority: P2 (open redirect / phishing) · Next action: confirm Automated maintainer review (advisory only — not an approval; merge authority stays with @marcelfolaron / @broskees). Reviewed at head Intent: Closes CWE-601 in This is the cleanest of the batch — it reuses an existing, presumably-tested helper rather than hand-rolling validation, and it also removes the duplicated empty-URL default. Two things to verify:
Acceptance checklist (must pass before merge):
CI: Can't read checks from here — confirm in the UI. If |
|
Copilots comment is valid. Oftentimes we will have the full absolute URL of the leantime instance in the var, so this would prevent the redirect. It needs to strip the base url first (or check and allow) |
Address maintainer review feedback from PR Leantime#3658: The login form often submits a full absolute URL (e.g. https://my-leantime.com/dashboard/home) in the redirectUrl hidden field. Previously resolveSafeRedirect() treated any valid URL (including same-origin absolute URLs) as external and rejected it, falling back to the default dashboard redirect. Fix: strip the BASE_URL prefix before the open-redirect guard so same-origin absolute URLs are treated identically to their relative counterparts. External URLs are still blocked. New tests: - Same-origin absolute URL → allowed (strips BASE_URL) - Same-origin absolute URL with deep path → allowed - URL-encoded same-origin absolute URL → allowed (urldecode first) - External URL with BASE_URL-like path prefix → rejected Co-Authored-By: Claude <noreply@anthropic.com>
| @@ -488,12 +488,20 @@ | |||
| if ($redirect !== null && trim($redirect) !== '' && trim($redirect) !== '/') { | |||
| $url = urldecode($redirect); | |||
| if (filter_var($url, FILTER_VALIDATE_URL) === false) { | ||
| $redirectUrl = BASE_URL.'/'.ltrim($url, '/'); | ||
| } elseif (in_array($url, ['/auth/logout'], true)) { | ||
| $redirectUrl = BASE_URL.$url; | ||
| } |
- Replace urldecode() with rawurldecode() to prevent + sign corruption in redirect URL query strings - Remove dead elseif branch for /auth/logout: the path is a relative URL (not a valid absolute URL), so it always hits the FILTER_VALIDATE_URL === false branch first, producing the same BASE_URL result Co-Authored-By: Claude <noreply@anthropic.com>
| // URL-encoded same-origin absolute URL — urldecode is called first, | ||
| // then BASE_URL is stripped. | ||
| $this->assertSame( |
| // Strip the application base URL when present so that same-origin | ||
| // absolute URLs (e.g. https://my-leantime.com/dashboard/home) are | ||
| // treated the same as their relative counterparts. The login form | ||
| // may submit a full absolute URL in the redirectUrl field. | ||
| if (str_starts_with($url, BASE_URL)) { |
Description
Fixes CWE-601 (URL Redirection to Untrusted Site) in the Login controller.
Vulnerability
The
Login::post()method redirects users to a user-controlledredirectparameter after login without validation. An attacker can craft a link that appears to go to the legitimate Leantime instance but redirects the victim to a phishing site after login.Impact (CVSS 6.1)
Fix
Used the existing
resolveSafeRedirect()helper (already used elsewhere in the codebase) to validate the redirect URL. Falls back to dashboard if the redirect parameter is invalid.PoC
Researcher: Javokhir Tursunboyev (@javokhir-sec)
Disclosure: Responsible, with fix PR