PyPI Verified URL Open Redirect Vulnerability Chain
SummaryThe vulnerability bug chain consists of a verification bypass, an internal redirect, an 2026-7-13 23:59:35 Author: github.com(查看原文) 阅读量:0 收藏

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 resolves the path traversals and sends the request to:
    https://pypi.org/p/..%5Caccount%5Clogout%5C%3Fnext=/%2509%252Fgithub.com%5Cpypi%5Cwarehouse/
  2. PyPI's /p/ redirect router unquotes the percent-encoding and responds with:
    Location: https://pypi.org/project/..\account\logout\?next=/%09%2Fgithub.com\pypi\warehouse
  3. The browser receives this redirect, treats the backslashes as forward slashes, and resolves the path traversal (/project/../), issuing a GET request to:
    https://pypi.org/account/logout/?next=/%09%2Fgithub.com\pypi\warehouse/
  4. PyPI's logout endpoint validates the parameter, then passes it to WebOb, which strips the %09 (tab) and redirects the user to the attacker-controlled destination (github.com\pypi\warehouse, which the browser again treats as / so github.com/pypi/warehouse).

Further Analysis

This vulnerability has been confirmed in Firefox, Chrome, and Edge. Safari appears to not be vulnerable.

The issues affected the PyPI web service and WebOb. The maintainers of both projects have addressed the issues:

  • PyPI has resolved the issue on the PyPI web service.
  • WebOb has released a fix in version 1.8.10 (tracked under GHSA-fh3h-vg37-cc95) which addresses the incomplete patch of CVE-2024-42353.

Timeline

Date reported: April 1, 2026
Date fixed: May 20, 2026 (PyPI), June 3, 2026 (WebOb 1.8.10)
Date disclosed: July 3, 2026


文章来源: https://github.com/google/security-research/security/advisories/GHSA-6hc9-758f-7r98
如有侵权请联系:admin#unsafe.sh