Security: Open redirect via unvalidated redirect_to param in SocialLanding page
## Summary
The `useSocialLandingHandler` hook in `packages/experience/src/pages/SocialLanding/use-social-landing-handler.ts` reads the `redirect_to` query parameter from the URL and passes it directly to `window.location.replace(new URL(redirectUri))` without validating that the destination is same-origin. This creates an open redirect vulnerability via the social sign-in landing page.
## Affected file
[`packages/experience/src/pages/SocialLanding/use-social-landing-handler.ts`](https://github.com/logto-io/logto/blob/master/packages/experience/src/pages/SocialLanding/use-social-landing-handler.ts)
## Vulnerable code
```ts
const socialLandingHandler = useCallback(
(connectorId: string) => {
const redirectUri = getSearchParameters(search, SearchParameters.RedirectTo);
if (!redirectUri) {
setLoading(false);
setToast(t('error.invalid_connector_request'));
return;
}
// ...
window.location.replace(new URL(redirectUri)); // ← no origin validation
},
[search, setToast, t]
);
```
`getSearchParameters` is a simple `URLSearchParams.get()` wrapper. The value is taken directly from `?redirect_to=` in the URL and passed to `window.location.replace` with no same-origin check.
## Impact
An attacker can craft a social landing URL pointing to an external domain:
```
https://auth.example.com/social-landing/google?redirect_to=https://attacker.com
```
After a user completes social authentication and lands on this page, they are silently redirected to the attacker-controlled domain. This enables phishing attacks where the user believes they have successfully authenticated and are being returned to the app, but instead are sent to a lookalike page.
## Fix
Validate that the redirect target is same-origin before redirecting:
```ts
const socialLandingHandler = useCallback(
(connectorId: string) => {
const redirectUri = getSearchParameters(search, SearchParameters.RedirectTo);
if (!redirectUri) {
setLoading(false);
setToast(t('error.invalid_connector_request'));
return;
}
// Validate same-origin to prevent open redirect
try {
const url = new URL(redirectUri);
if (url.origin !== window.location.origin) {
setLoading(false);
setToast(t('error.invalid_connector_request'));
return;
}
window.location.replace(url);
} catch {
setLoading(false);
setToast(t('error.invalid_connector_request'));
}
},
[search, setToast, t]
);
```
## Note on existing validation
The `useRedirectCallbackValidation` hook (`use-redirect-callback-validation.ts`) in the same codebase validates redirect URIs against registered app redirect URIs — but that validation applies to the final OIDC `redirect_uri`, not to this intermediate social landing `redirect_to` parameter.
The `useGlobalRedirectTo` hook (`use-global-redirect-to.ts`) intentionally allows external URLs (for social provider redirects and OIDC callbacks to registered app URIs) — but the social landing `redirect_to` parameter should be Logto-internal and same-origin.
## Environment
- Logto version: latest (`svhd/logto:latest`, digest `sha256:9dc15595766961d0d81d1026fc38294eacf3ce32d6dab2e9dbeb6d9b10e7a031`)
- Confirmed in minified bundle `assets/index-BpICSbS-.js` and source `use-social-landing-handler.ts`
0 条评论