agentggagentgg
Back to all findings
MEDIUMconfirmedurl-regex-validationopen-redirect-includes3821c3958ee8

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.

Filelib/insecurity.ts
Lines128134
Confidence
98%
File statusvalidated
Details

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)).

Proof of concept

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.

Impact

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.

Validation
confirmed

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.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Base score: 6.1 · MEDIUM

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.

References