Open redirect via substring includes check in isRedirectAllowed
isRedirectAllowed uses url.includes(allowedUrl) to validate redirect targets, allowing attackers to bypass the allowlist by embedding an allowlisted URL as a query parameter or path segment.
In lib/insecurity.ts lines 128–134:
export const isRedirectAllowed = (url: string) => {
let allowed = false
for (const allowedUrl of redirectAllowlist) {
allowed = allowed || url.includes(allowedUrl)
}
return allowed
}
The function iterates over the redirectAllowlist Set and uses String.prototype.includes to check whether the input URL contains any allowlisted URL as a substring. This is exactly the unsafe pattern described in the criteria (criterion 3: includes(domain) used as a URL safety gate). Because includes matches anywhere in the string, an attacker can place an allowlisted URL anywhere in their malicious URL (typically in a query parameter, fragment, or userinfo segment) and still pass the check.
A safe implementation should parse the URL with new URL() and compare the parsed origin/hostname exactly against an allowlist (e.g. ALLOWED.has(u.origin)).
Given the allowlist entry https://github.com/juice-shop/juice-shop, the following attacker-controlled URL passes the check:
isRedirectAllowed('https://evil.example.com/?to=https://github.com/juice-shop/juice-shop') returns true.
If this is used to gate a redirect endpoint (e.g. /redirect?to=...), browsing to:
https://victim/redirect?to=https://evil.example.com/?to=https://github.com/juice-shop/juice-shop
will redirect the victim to https://evil.example.com/..., enabling phishing or token theft.
Unauthenticated open redirect / allowlist bypass. Any consumer of isRedirectAllowed (e.g. the redirect endpoint in OWASP Juice Shop) can be tricked into redirecting users to attacker-controlled domains, aiding phishing, OAuth/token leakage via Referer, and bypass of anti-abuse filters that trust the allowlist.
The isRedirectAllowed function at lines 128–134 uses url.includes(allowedUrl) inside a loop over redirectAllowlist, which is exactly the unsafe substring-match pattern. An attacker can craft a URL like https://evil.example.com/?to=https://github.com/juice-shop/juice-shop that returns true, since the allowlisted string appears anywhere in the input. A safe implementation would parse via new URL() and compare origins/hostnames exactly. The function is exported and consumed by the redirect route, making the open-redirect chain reachable from unauthenticated input.
The flawed isRedirectAllowed check at lines 128–134 uses url.includes(allowedUrl), which any unauthenticated attacker can bypass by embedding an allowlisted URL (e.g. https://github.com/juice-shop/juice-shop) in a query string or fragment of an attacker-controlled URL — exploitable purely by crafting a link (AV:N, AC:L, PR:N), but it requires the victim to click that link (UI:R). Scope is Changed because the redirect lands the victim in a different security authority (the attacker's origin), where phishing and OAuth/Referer token leakage occur outside the Juice Shop component. Confidentiality and Integrity are Low: the attacker can induce credential disclosure or trick the user into actions on a spoofed page via the trusted redirector, but cannot directly read or modify Juice Shop data; availability is unaffected.
- CWE-601
- OWASP A01:2021 - Broken Access Control
- OWASP Unvalidated Redirects and Forwards Cheat Sheet