Skip to content

fix(console): validate continue param in /auth/authorize to prevent open redirect (CWE-601)#26507

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

fix(console): validate continue param in /auth/authorize to prevent open redirect (CWE-601)#26507
sebastiondev wants to merge 1 commit intoanomalyco:devfrom
sebastiondev:fix/cwe601-authorize-open-ca51

Conversation

@sebastiondev
Copy link
Copy Markdown

Issue for this PR

Closes #26506

(Tracking issue intentionally describes the change at a high level only — exploit details are kept out of the public issue per responsible-disclosure practice. Happy to redirect through a GitHub Security Advisory if maintainers prefer.)

Type of change

  • Bug fix (security)
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Re-submission of #26373, which was auto-closed by the pr-standards bot for not having a linked issue. Issue #26506 now provides that link without disclosing exploit details publicly.

The console's /auth/authorize route concatenates the user-supplied continue query parameter directly into the OAuth callback URL without validation. After the OAuth round-trip, the callback handler derives a redirect target from the callback path, which means a crafted continue value can redirect the user to an attacker-controlled origin once authentication completes.

Affected file: packages/console/app/src/routes/auth/authorize.ts

Data flow:

  1. /auth/authorize?continue= reads continue from the query string.
  2. It builds callbackUrl = new URL("./callback" + cont, request.url) and passes it to AuthClient.authorize(...) as the OAuth redirect_uri.
  3. After the IdP round-trip, [...callback] computes next = url.pathname.replace("/auth/callback","") and calls redirect(route(locale, next)).
  4. route() returns next verbatim for paths it doesn't specifically rewrite, so a next of //evil.com/x is passed through.
  5. Browsers resolve a Location: //evil.com/x response as https://evil.com/x — the user is redirected off-site.

The fix adds a safeContinue() validator at the entry point that only accepts values which:

  • begin with a single / (relative path, not protocol-relative),
  • do not start with //, \, or /\ (protocol-relative / backslash variants that some browsers normalize),
  • contain no .., backslashes, or CR/LF/TAB characters.

Anything else is replaced with an empty string, which produces the safe default callback URL /auth/callback. The change is 18 lines, scoped to the single vulnerable file, and preserves the legitimate behavior of continue for in-app post-login navigation.

function safeContinue(value: string): string {
  if (!value) return ""
  if (!value.startsWith("/")) return ""
  if (value.startsWith("//") || value.startsWith("/\\") || value.startsWith("\\")) return ""
  if (value.includes("..") || value.includes("\\") || /[\r\n\t]/.test(value)) return ""
  return value
}

How did you verify your code works?

Walked the validator against each attack class and the legitimate inputs by hand:

Input Result Notes
"" "" default callback
/workspace/abc /workspace/abc legitimate path preserved
//evil.com/x "" protocol-relative blocked
/\evil.com "" backslash variant blocked
\\evil.com "" backslash variant blocked
/foo/../../bar "" traversal blocked
https://evil.com "" absolute URL blocked (no leading /)
value containing CR/LF "" header injection blocked

The rest of the auth flow is untouched, so existing OAuth behavior for valid relative continue values is preserved.

Screenshots / recordings

N/A — no UI change.

Checklist

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

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.

Harden console /auth/authorize handling of the continue parameter

1 participant