Quick answer
A conversational AI platform validated the OAuth login's redirect_url parameter by checking that it started with /, not with //, and did not contain :. The backslash passed those three checks. The browser normalizes /\evil.tld to //evil.tld → https://evil.tld, and the server issued the post-login redirect to the attacker's domain with the session cookie already established.
1. Context — the post-login redirect flow
The https://main.target.tld/login endpoint accepted a redirect_url parameter indicating where to send the user after completing login. This parameter was embedded in the OAuth state (googleOauthState / appleOauthState) as the next_url field. After the OAuth provider (Google or Apple) completed authentication and redirected back, the server read next_url from the state and issued the final redirect.
The OAuth state could be seen in __NEXT_DATA__ by decoding the base64 of googleOauthState:
{
"csrf": "6d7f9f064956d71d4f89a20b987e589d",
"platform": "web",
"next_url": "/\\evil.tld",
"current_url": "/login"
}
2. The bypass — unfiltered backslash
The vulnerable validation
The client-side redirect_url validation code checks three conditions:
if (
redirectUrl.startsWith("/") && // debe ser path relativo
!redirectUrl.startsWith("//") && // bloquea protocol-relative URLs
!redirectUrl.includes(":") // bloquea javascript: y esquemas externos
)
Why the backslash bypasses it
The attacker's input is /\evil.tld (URL-encoded as /%5Cevil.tld):
| Check | Result | Reason |
|---|---|---|
startsWith("/") | ✅ passes | Starts with / |
!startsWith("//") | ✅ passes | It's /\, not // |
!includes(":") | ✅ passes | There's no colon |
| Result | ACCEPTED | The validator treats it as a relative path |
But browsers normalize the backslash to a slash per the URL spec:
/\evil.tld → //evil.tld → https://evil.tld
The server embeds the value unchanged in the OAuth state's next_url. When issuing the post-login redirect, the browser receives /\evil.tld and resolves it as an external URL.
3. The attack URL
https://main.target.tld/login?redirect_url=/%5Cevil.tld
%5C is the URL encoding of the backslash \. When decoded, the parameter becomes /\evil.tld.
4. Base attack flow
Atacante envía link: target.tld/login?redirect_url=/%5Cevil.tld
↓
Víctima abre el link (página de login real)
↓
redirect_url pasa validación (/\ no es //)
↓
Servidor embebe next_url: "/\evil.tld" en googleOauthState
↓
Víctima hace click "Continue with Google" → autenticación
↓
OAuth callback con código válido
↓
Servidor lee next_url del estado → "/\evil.tld"
↓
Redirect a /\evil.tld (cookie de sesión ya establecida)
↓
Navegador normaliza /\ a // → https://evil.tld
↓
Víctima llega al dominio del atacante con sesión activa
5. Escalation chain — Open Redirect via Canvas + postMessage
In the report's escalation, a second chain is documented that combines this open redirect with the platform's canvas system openUrl handler, turning it into a stored and zero-click vector.
How openUrl works in the canvas
A bot's canvas can send a postMessage to the parent with type: "openUrl". Normally, when the destination URL is external, the platform shows the user a confirmation overlay before opening the URL. The shouldAutoOpen function decides whether to show that overlay or not.
The overlay bypass
shouldAutoOpen whitelists the main domain as a trusted origin and skips the confirmation overlay for URLs on that domain:
openUrl → https://main.target.tld/login?redirect_url=/%5Cevil.tld
↓
shouldAutoOpen: hostname === "main.target.tld" → true → window.open() sin confirmación
↓
La plataforma procesa el login con next_url = /\evil.tld
↓
Redirect a evil.tld
Since the URL passed to the handler is on the whitelisted domain, it passes as a trusted URL. The overlay is not shown. The window opens directly and the redirect chain runs automatically.
Result of the full chain
- The bot appears on the platform's homepage and suggestions organically.
- The user clicks the bot (normal platform behavior).
- The canvas executes the postMessage with
openUrl. shouldAutoOpentreats it as a trusted URL → no overlay.- The redirect to the external domain fires without any additional confirmation.
6. Behavior with an already-authenticated user
If the user already has an active session, the login endpoint doesn't ask for credentials — it redirects them directly to next_url. This means the open redirect also works on already-logged-in users who open the link, without needing to go through the OAuth flow.
7. Technical classification
| Field | Value |
|---|---|
| Vulnerability type | Open Redirect — Incomplete URL Validation (Backslash Normalization) |
| CWE | CWE-601 — URL Redirection to Untrusted Site |
| Affected endpoint | https://main.target.tld/login?redirect_url= |
| Vulnerable parameter | redirect_url → embedded in the OAuth state's next_url |
| Affected browsers | Firefox 149, Chrome 146 (standard normalization) |
| Required interaction (base) | Victim opens the link and completes login |
| Required interaction (canvas chain) | A single click on the bot |
8. Technical notes
- The normalization of
\to/in URL paths is standard behavior of the URL parser (WHATWG URL spec). It's not specific to any browser — all modern browsers do it. - The client validator checks
//but not\— the fix is to add!redirectUrl.includes("\\")to the set of checks. - The validation is applied only on the client. The server does not re-validate
next_urlbefore issuing the post-OAuth redirect, which is where the actual redirect happens. - In the canvas chain, the confirmation overlay bypass is independent of the open redirect — it's a logic flaw in the whitelist that doesn't account for URLs on that domain being able to redirect externally.
Related labs
Practice open redirect chains, parser quirks (backslash, dot, @) and URL validation bypasses: Open Redirect labs.
Practice this in a lab
Open Redirect
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
OAuth open redirect — backslash bypass of redirect_uri (POE report walkthrough)
Walkthrough of the POE report: open redirect via `\` (backslash) in redirect_uri that POE's parser didn't normalize correctly. URL-based XSS chained for token theft.
OAuth attacks — state CSRF, redirect_uri bypass, token reuse, token IDOR
Vulnerabilities in OAuth 2.0 flows: missing state parameter, redirect_uri loose validation, cross-app token reuse, IDOR in token refresh endpoints.
File Upload — extension, content-type and magic bytes bypasses
10 bypasses to upload webshells: double extension, null byte, content-type spoof, magic bytes, polyglots, race conditions and path traversal abuse.