Open redirects let attackers craft URLs on your domain that redirect to malicious sites. Users trust your domain, click the link, and end up on a phishing page.
Key Takeaways
- 1Open redirects enable phishing with your domain's trust
- 2Never redirect to user-controlled URLs without validation
- 3Use allowlists for redirect destinations
- 4Prefer relative paths over absolute URLs
- 5OAuth redirect_uri is a common attack vector
"An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials."
The Vulnerability
Open redirect vulnerabilities occur when applications redirect users to URLs taken directly from user input without validation. This seemingly minor issue is listed in the OWASP Top 10 and is frequently exploited in sophisticated phishing campaigns targeting high-value accounts.
// VULNERABLE: Direct redirect to user input
app.get('/redirect', (req, res) => {
const target = req.query.url;
res.redirect(target); // Attacker controls destination!
});
// Attack URL:
// https://trusted.com/redirect?url=https://evil.com/phishing
// User sees trusted.com, clicks, ends up on evil.comThe vulnerable code above takes a URL parameter and redirects to it without any validation. Attackers craft URLs using your trusted domain that redirect victims to phishing pages. When users see a link to trusted.com, they click without hesitation, landing on a malicious site that mimics your login page.
Understanding the real-world impact of open redirects helps justify the effort required to prevent them. These attacks cause measurable harm to both users and organizations.
Attack Impact
Open redirects enable several attack types, each with serious consequences. The table below outlines the primary threats and their potential business impact. In high-profile breaches, attackers have combined open redirects with social engineering to compromise enterprise credentials.
| Attack | Impact |
|---|---|
| Phishing | Steal credentials via fake login page |
| Malware delivery | Redirect to drive-by download |
| OAuth token theft | Capture authorization codes |
| Trust exploitation | Link appears safe, destination isn't |
OAuth token theft is particularly dangerous in enterprise environments. Attackers can steal authorization codes by redirecting the OAuth callback to their server, gaining access to user accounts without ever touching credentials. Now let's look at proven prevention techniques.
Prevention
Multiple defense strategies exist, and the best choice depends on your use case. Allowlisting provides the strongest protection for applications with known redirect destinations. When you need more flexibility, relative paths or indirect references offer good security without maintaining explicit lists.
// 1. Allowlist destinations
const ALLOWED_REDIRECTS = [
'https://example.com',
'https://app.example.com',
'https://docs.example.com',
];
function isSafeRedirect(url) {
try {
const parsed = new URL(url);
return ALLOWED_REDIRECTS.some(allowed => {
const base = new URL(allowed);
return parsed.origin === base.origin;
});
} catch {
return false;
}
}
// 2. Use relative paths only
app.get('/redirect', (req, res) => {
const path = req.query.path;
// Must start with / and not //
if (!path.startsWith('/') || path.startsWith('//')) {
return res.status(400).send('Invalid redirect');
}
res.redirect(path); // Relative to current origin
});
// 3. Use indirect references
const REDIRECT_MAP = {
'dashboard': '/app/dashboard',
'profile': '/app/profile',
'settings': '/app/settings',
};
app.get('/goto/:key', (req, res) => {
const target = REDIRECT_MAP[req.params.key];
if (!target) return res.status(404).send('Not found');
res.redirect(target);
});The code shows three prevention strategies. Allowlisting (method 1) validates against a list of approved origins. Relative paths (method 2) restrict redirects to your own domain by requiring URLs to start with a single slash. Indirect references (method 3) use opaque keys instead of URLs, completely removing user control over destinations.
Even with prevention measures in place, attackers constantly find creative bypasses. Understanding these techniques helps you validate that your implementation is truly secure.
Common Bypasses
Attackers have developed numerous techniques to bypass open redirect protections. These bypasses exploit assumptions in validation code: that URLs always start with a protocol, that the hostname is always before the path, or that certain characters aren't significant. Testing against these patterns is essential.
# These might bypass naive checks:
//evil.com # Protocol-relative
https://trusted.com@evil.com # Userinfo confusion
https://evil.com#trusted.com # Fragment confusion
https://evil.com?trusted.com # Query confusion
https://trusted.com.evil.com # Subdomain of evil.com
http://trusted.com%00.evil.com # Null byte injection
https://trusted.com\@evil.com # Backslash confusion
# Always parse with URL API and check hostname explicitlyProtocol-relative URLs (//evil.com) are interpreted as external URLs with the current page's protocol. Userinfo confusion places the attacker's domain after an @ symbol, which the URL parser interprets as the actual host. Fragment and query confusion exploit visual similarity to trick users. Always use the URL API to parse and explicitly check the resulting hostname property.
With a clear understanding of bypass techniques, you can implement a robust redirect handler that resists these attacks.
Secure Implementation
A production-ready redirect implementation needs multiple layers of protection. The following function demonstrates a defense-in-depth approach: it validates input format, resolves the URL against the current host, and verifies the result points to the same origin before redirecting.
function secureRedirect(req, res) {
const returnTo = req.query.return_to;
// Default fallback
if (!returnTo) {
return res.redirect('/');
}
// Must be relative path
if (!returnTo.startsWith('/') || returnTo.startsWith('//')) {
console.warn('Blocked open redirect attempt:', returnTo);
return res.redirect('/');
}
// Additional check: resolve and verify
try {
const resolved = new URL(returnTo, `https://${req.headers.host}`);
if (resolved.host !== req.headers.host) {
return res.redirect('/');
}
res.redirect(resolved.pathname + resolved.search);
} catch {
res.redirect('/');
}
}This implementation first checks for a missing return URL and falls back to the homepage. The format check blocks protocol-relative URLs (starting with //). The key insight is resolving the path against the current host, then verifying the resolved URL's host matches. Any validation failure safely redirects to the homepage rather than exposing an error message that could aid attackers.