CVE-2023-39968 (published 2023-08-28T21:15Z)

jupyter-server is the backend for Jupyter web applications. Open Redirect Vulnerability. Maliciously crafted login links to known Jupyter Servers can cause successful login or an already logged-in session to be redirected to arbitrary sites, which should be restricted to Jupyter Server-served URLs. This issue has been addressed in commit `29036259` which is included in release 2.7.2. Users are advised to upgrade. There are no known workarounds for this vulnerability.

CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/jupyter-server/jupyter_server
commit_details: repo_url: https://github.com/jupyter-server/jupyter_server
author: Min RK
author_date: 2023-08-18 05:27:27+02:00
msg: Merge pull request from GHSA-r726-vmfq-j9j3

Co-authored-by: Zachary Sailer <zsailer@apple.com>
repository: repo_url: https://github.com/jupyter-server/jupyter_server
repo_name: jupyter-server/jupyter_server
description: The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications.
repo_language: Python
owner: jupyter-server
file_changes: filename: login.py
change_type: ModificationType.MODIFY
diff: @@ -41,12 +41,25 @@ class LoginFormHandler(JupyterHandler):
         # \ is not valid in urls, but some browsers treat it as /
         # instead of %5C, causing `\\` to behave as `//`
         url = url.replace("\\", "%5C")
+        # urllib and browsers interpret extra '/' in the scheme separator (`scheme:///host/path`)
+        # differently.
+        # urllib gives scheme=scheme, netloc='', path='/host/path', while
+        # browsers get scheme=scheme, netloc='host', path='/path'
+        # so make sure ':///*' collapses to '://' by splitting and stripping any additional leading slash
+        # don't allow any kind of `:/` shenanigans by splitting on ':' only
+        # and replacing `:/*` with exactly `://`
+        if ":" in url:
+            scheme, _, rest = url.partition(":")
+            url = f"{scheme}://{rest.lstrip('/')}"
         parsed = urlparse(url)
-        if parsed.netloc or not (parsed.path + "/").startswith(self.base_url):
+        # full url may be `//host/path` (empty scheme == same scheme as request)
+        # or `https://host/path`
+        # or even `https:///host/path` (invalid, but accepted and ambiguously interpreted)
+        if (parsed.scheme or parsed.netloc) or not (parsed.path + "/").startswith(self.base_url):
             # require that next_url be absolute path within our path
             allow = False
             # OR pass our cross-origin check
-            if parsed.netloc:
+            if parsed.scheme or parsed.netloc:
                 # if full URL, run our cross-origin check:
                 origin = f"{parsed.scheme}://{parsed.netloc}"
                 origin = origin.lower()

programming_language: Python
filename: test_login.py
change_type: ModificationType.MODIFY
diff: @@ -92,6 +92,7 @@ def login(jp_serverapp, http_server_client, jp_base_url, login_headers):
         "//host{base_url}tree",
         "https://google.com",
         "/absolute/not/base_url",
+        "https:///a%40b/extra/slash",
     ),
 )
 async def test_next_bad(login, jp_base_url, bad_next):

programming_language: Python