Description
Vulnerability Overview
- In the OAuth callback handler (GET /auth/callback), the server trusts the X-Forwarded-Host and X-Forwarded-Proto headers as-is to construct the final redirect URL.
- These headers are meant to be set or rewritten by a reverse proxy. However, in environments where client-injected values are propagated to the application, an attacker can redirect users to an arbitrary external domain.
- In Burp Suite Repeater, injecting X-Forwarded-Host: google.com changed the response Location to http://google.com/auth/redirect, confirming an open redirect.
Vulnerable Code
https://github.com/onlook-dev/onlook/blob/53ec325c06bc026a0fe64fde69070b3fe7b0cb90/apps/web/client/src/app/auth/callback/route.ts#L37-L44
const forwardedHost = request.headers.get('x-forwarded-host');
// Redirect to the redirect page which will handle the return URL
if (forwardedHost) {
const forwardedProto = request.headers.get('x-forwarded-proto') || 'https';
return NextResponse.redirect(`${forwardedProto}://${forwardedHost}${Routes.AUTH_REDIRECT}`);
} else {
return NextResponse.redirect(`${origin}${Routes.AUTH_REDIRECT}`);
}
- taint source: request.headers.get('x-forwarded-host') — reads a user-controlled header as-is
- taint propagation: assembles an absolute URL (proto://host/path) via string templating
- sink: NextResponse.redirect(absoluteUrl) — issues a 30x redirect to an external domain
- missing defenses: no host allowlist or same-origin validation, no forced relative-path redirects, and no assurance that trusted proxies rewrite these headers
PoC
PoC Description
- We completed the GitHub OAuth login flow and captured the latest code issued just before Supabase redirected back to the app.