Skip to content
GoogleGHSA-6hc9-758f-7r98

PyPI Verified URL Open Redirect Vulnerability Chain

MediumPublished Jul 14, 2026

### Summary The vulnerability bug chain consists of a verification bypass, an internal redirect, and an open redirect affecting PyPI (web service) and WebOb. This series of vulnerabilities undermines the trustworthiness of verified URLs and allows for the execution of unauthorized redirects. ### Severity **Medium** - The individual bugs allow an attacker to craft URLs that are successfully verified by trusted publishers. By undermining the verification of URLs, attackers can launch highly plausible phishing attacks with legitimate-looking lures. ### Proof of Concept To reproduce this vulnerability, an attacker chains three distinct behaviors to create a malicious, yet "verified", Project URL on PyPI. **Step 1: The URL Verification Bypass (PyPI)** PyPI automatically considers any self-referential link (e.g., `https://pypi.org/project/myproject/`) as a verified link. During upload, PyPI uses the `rfc3986` package to parse and normalize URLs. However, most browsers (Chrome, Firefox, Edge) follow the WHATWG URL standard, which tolerates backslashes `\` and treats them as `/` for schemes like `http` and `https`. By crafting a URL with backslashes, an attacker can trick `rfc3986` int...

GitHub advisory

Affected versions

PackageAffectedFixed in
WebOb
PyPI
< 1.8.101.8.10
Details and references

### Summary The vulnerability bug chain consists of a verification bypass, an internal redirect, and an open redirect affecting PyPI (web service) and WebOb. This series of vulnerabilities undermines the trustworthiness of verified URLs and allows for the execution of unauthorized redirects. ### Severity **Medium** - The individual bugs allow an attacker to craft URLs that are successfully verified by trusted publishers. By undermining the verification of URLs, attackers can launch highly plausible phishing attacks with legitimate-looking lures. ### Proof of Concept To reproduce this vulnerability, an attacker chains three distinct behaviors to create a malicious, yet "verified", Project URL on PyPI. **Step 1: The URL Verification Bypass (PyPI)** PyPI automatically considers any self-referential link (e.g., `https://pypi.org/project/myproject/`) as a verified link. During upload, PyPI uses the `rfc3986` package to parse and normalize URLs. However, most browsers (Chrome, Firefox, Edge) follow the WHATWG URL standard, which tolerates backslashes `\` and treats them as `/` for schemes like `http` and `https`. By crafting a URL with backslashes, an attacker can trick `rfc3986` into seeing a safe, self-referential path, while the victim's browser will resolve a path traversal. For example: `https://pypi.org/project/verified/..\x/../unverified\x/../` * **`rfc3986` sees:** `https://pypi.org/project/verified/` *(It does not treat `\` as `/`, so it interprets `..\x` as a literal folder name. When followed by `/../`, it traverses back up, effectively erasing the malicious segments and returning the base self-referential URL).* * **Browser sees:** `https://pypi.org/project/unverified/` *(It treats `\` as `/`, so `..\x/../` is interpreted as `../x/../`. This navigates up to `/project/`, down to `/project/x/`, up to `/project/`, and down to `/project/unverified/`).* **Step 2: The Internal Redirect Bridge (PyPI)** To trigger an open redirect, the attacker needs to reach an endpoint that accepts a query string (like `/account/logout/?next=...`), but the verification bypass does not allow query strings. To bridge this gap, the attacker targets the permissive internal redirect route `/p/{name}/{version}/`. Because this route matches `[^/]+` and unquotes percent-encoding, the attacker can pack the query string into the `{name}` and `{version}` path segments using URL encoding. **Step 3: The Open Redirect (WebOb / PyPI)** PyPI's logout page reads the `next` parameter and validates it using `is_safe_url()`. PyPI attempts to block control characters at the start of a URL, but because the crafted payload begins with a forward slash (e.g., `/%09/evil.com/`), it bypasses this check. `urllib3.util.parse_url` sees no scheme or host, so `is_safe_url()` incorrectly returns `True`. Pyramid then uses the `webob.Response` class to execute the redirect. WebOb contains an existing protection (from CVE-2024-42353) that checks if the location `startswith("//")`. Because our payload starts with `/\t/`, it bypasses this check and is passed directly to `urllib.parse.urljoin(base, url)`. In Python 3.10+, `urlsplit()` strips ASCII newline and tab characters entirely. Thus, `urljoin("https://pypi.org/", "/\t/evil.com/")` evaluates effectively as `urljoin("https://pypi.org/", "//evil.com/")`. This turns the relative path into a scheme-relative absolute URL, replacing the base URL entirely and returning `https://evil.com/`. **Actionable Reproduction:** 1. Create a Python package (e.g., `cblinktest`). 2. In the package metadata, add a Project URL (e.g., "Homepage") with the following full exploit payload: `https://pypi.org/p/cblinktest/..\x/../..%5Caccount%5Clogout%5C%3Fnext=\%2509%252Fgithub.com%5Cpypi%5Cwarehouse\x/..` 3. Upload the package to PyPI. 4. Navigate to the project's PyPI page and observe that the URL has a "Verified" checkmark. 5. Click the link. **Resolution flow when clicked:** 1. The browser re

Severity from
GitHub (reviewed advisory)

More Google advisories

All Google
Advisory
Google Chrome: use after free
Critical9.6Jul 14
Google Chrome: use after free
High8.3Jul 14
Google Chrome: origin validation error
Medium6.5Jul 14
Google Chrome: remote code execution
High8.8Jul 14
Google Chrome: use after free
High7.5Jul 14
Google Chrome: improper input validation
Medium6.5Jul 14

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.