Description
A client-side open redirect occurs when a user-controlled value, such as a query parameter, fragment or DOM value, is passed directly to location.assign, replace or href. Allowing an unvalidated external destination can send a user from a trusted site to an attacker's site for phishing or token theft. For example, a manipulated to value can cause automatic navigation after the trusted page loads.
Potential impact
- An attacker may lure users into entering credentials or MFA codes on a phishing site.
- An open-redirect chain may help bypass OAuth redirect validation and expose authorization codes or access tokens.
- A trusted link may lead to malicious downloads and harm trust in the service.
- Navigation may bypass referrer-based filtering or other destination checks.
Remediation
- Do not use user input directly as a redirect destination.
- Map allow-listed keys such as
to=hometo internal paths, and send unknown values to a default path. - Accept only internal destinations. Reject absolute URLs with a scheme or host, and protocol-relative URLs such as
//evil.com. - If parsing a value with
new URL(value, location.origin), check the resulting origin against the current site and require a pathname beginning with/. - Apply the same allow-list on the server when it performs the final redirect.
Examples
Before
javascript
// Use the fragment directly as an external redirect destination
function unsafeRedirect() {
// URL: https://trusted.example/app#to=https://evil.example
const raw = location.hash.replace(/^#to=/, '');
if (raw) {
// A user-controlled absolute URL can navigate outside the site
top.location = decodeURIComponent(raw);
}
}
After
javascript
// Map allow-listed keys only to internal redirect paths
const ROUTE_MAP = Object.freeze({
home: '/home',
dashboard: '/dashboard',
help: '/help'
});
function safeRedirect() {
const params = new URLSearchParams(window.location.search);
const key = params.get('to');
const path = Object.hasOwn(ROUTE_MAP, key) ? ROUTE_MAP[key] : undefined;
if (path) {
// Navigate only to an internal path, without a supplied scheme or host
window.location.assign(path);
} else {
// Use the default path for unsupported values
window.location.replace('/home');
}
}
Explanation:
- Before: The fragment becomes a navigation destination, including absolute or protocol-relative external URLs. An attacker can place
#to=and an external URL in a trusted site's link to redirect the user. - After: Input selects only an own key in the route allow-list, which maps to an internal path. Inherited properties such as
toStringare excluded. External URLs are not available as destinations on this path.