Problem
A common pattern for validating URLs in server-side proxy routes uses endsWith to check the hostname against an allowlist. This is vulnerable to SSRF bypass because an attacker can register a domain that ends with the same string:
// Vulnerable validation
const allowedDomain = "bandsintown.com";
function isAllowed(url: string): boolean {
const hostname = new URL(url).hostname;
return hostname.endsWith(allowedDomain);
}
isAllowed("https://bandsintown.com/api/events"); // true (intended)
isAllowed("https://api.bandsintown.com/api/events"); // true (intended)
isAllowed("https://evil-bandsintown.com/api/events"); // true (SSRF bypass!)
isAllowed("https://attackerbandsintown.com/exfil"); // true (SSRF bypass!)
An attacker registers evil-bandsintown.com and uses your server as a proxy to make requests to their controlled infrastructure or internal services.
Solution
Option 1: Exact match plus dot-prefixed subdomain check (recommended)
function isAllowedHostname(url: string, allowedDomain: string): boolean {
const hostname = new URL(url).hostname;
return hostname === allowedDomain || hostname.endsWith(`.${allowedDomain}`);
}
isAllowedHostname("https://bandsintown.com/api", "bandsintown.com"); // true
isAllowedHostname("https://api.bandsintown.com/api", "bandsintown.com"); // true
isAllowedHostname("https://evil-bandsintown.com/api", "bandsintown.com"); // false
Option 2: Validate against a full origin allowlist
const ALLOWED_ORIGINS = new Set([
"https://bandsintown.com",
"https://api.bandsintown.com",
]);
function isAllowedOrigin(url: string): boolean {
const { origin } = new URL(url);
return ALLOWED_ORIGINS.has(origin);
}
Why It Works
endsWith('bandsintown.com') is a pure string operation with no understanding of DNS hierarchy. The string evil-bandsintown.com ends with bandsintown.com, so the check passes. Adding a dot prefix (.bandsintown.com) ensures that only actual subdomains match, because DNS labels are separated by dots. The exact match check (hostname === domain) handles the root domain case.
Option 2 is even stricter because it validates the full origin (scheme + host + port), which also prevents protocol downgrade attacks (e.g., http:// vs https://).
Context
- OWASP classifies this as a Server-Side Request Forgery (SSRF) vulnerability
- Commonly found in API proxy routes, webhook URL validation, and OAuth redirect URI checks
- Also applies to
startsWithchecks on URL paths -- use a URL parser instead of string operations - Always use
new URL()to parse URLs rather than regex or string splitting, which can be tricked by malformed URLs likehttps://evil.com@bandsintown.com - Consider also validating that the resolved IP address is not in a private range (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) to prevent DNS rebinding attacks