Skip to content

fix(console): prevent open redirect in /auth/authorize continue parameter#37809

Open
sebastiondev wants to merge 1 commit into
anomalyco:devfrom
sebastiondev:fix/cwe601-authorize-open-ca51
Open

fix(console): prevent open redirect in /auth/authorize continue parameter#37809
sebastiondev wants to merge 1 commit into
anomalyco:devfrom
sebastiondev:fix/cwe601-authorize-open-ca51

Conversation

@sebastiondev

Copy link
Copy Markdown

Issue for this PR

Closes #37807

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Fixes an open redirect (CWE-601) in the console /auth/authorize route.

The bug. packages/console/app/src/routes/auth/authorize.ts reads the continue query parameter and concatenates it into a URL constructor:

const cont = url.searchParams.get("continue") ?? ""
const callbackUrl = new URL(`./callback${cont}`, input.request.url)

If cont = "//evil.com", the WHATWG URL parser resolves this to https://<host>/auth/callback//evil.com — still on the console origin, so it passes the OAuth provider's callback origin check. The OAuth flow then redirects the browser back to that path with a code, and the splat handler at packages/console/app/src/routes/auth/[...callback].ts computes:

const next = url.pathname.replace("/auth/callback", "") // => "//evil.com"
return redirect(route(locale, next))

redirect("//evil.com") emits a protocol-relative Location: //evil.com header. Browsers follow this to https://evil.com/, so the victim — who thinks they just logged in to the legitimate console — ends up on the attacker's site. Classic phishing amplifier because the initial URL is on the real console origin.

The fix. Two layers in authorize.ts:

  1. safeContinue() rejects any continue that is not a safe relative path: must start with a single /, must not start with // (protocol-relative), must not contain backslashes or control chars (some parsers treat \ like /), and must not contain .. segments.
  2. After resolving callbackUrl, we verify the result is still on the same origin and its pathname still starts with /auth/callback. If not, return 400. This catches anything the input filter missed (defence in depth).

Invalid continue values fall back to an empty string, so the normal flow (callback URL = /auth/callback) still works.

Nothing else is touched — the change is 26 lines added, 1 removed, all in authorize.ts.

How did you verify your code works?

  • Manually reproduced the vulnerability against the current dev code by hitting /auth/authorize?continue=//evil.com and confirming the resolved callbackUrl was https://<host>/auth/callback//evil.com, which after the callback stage produced a Location: //evil.com header (browser follows to the attacker origin).
  • Re-ran the same request against the patched code: safeContinue returns "", callbackUrl becomes .../auth/callback, redirect target is the real console. Other rejected inputs I tried: \\evil.com, /..//evil.com, /foo\r\nLocation: evil — all now normalise to empty and go through the safe path.
  • Legitimate values (continue=/team/foo, continue=/settings?tab=x) still resolve to callbackUrl = /auth/callback/team/foo etc. and pass the origin+prefix check.
  • Local unit checks around safeContinue pass (8/8 cases covering the reject/accept matrix above).

Before submitting, I tried to disprove this: I checked whether any router-level middleware, CSRF token, or OAuth state/redirect_uri allow-list on the provider side would block the attack. The console route has no such gate — it is intentionally an unauthenticated login entry point — and the provider only checks that the callback origin matches, which //evil.com preserves. So the redirect really does land on the attacker.

Screenshots / recordings

N/A — server-side redirect header change, no UI.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Discovered by the Sebastion AI GitHub App.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Open redirect in console /auth/authorize via continue parameter (CWE-601)

1 participant