Summary
jupyter-server-proxy is used to expose ports local to a Jupyter server listening to web traffic to the Jupyter server's authenticated users by proxying web requests and websockets. Dependent packages (partial list) also use jupyter-server-proxy to expose other popular interactive applications (such as RStudio, Linux Desktop via VNC, Code Server, Panel, etc) along with the Jupyter server. This feature is commonly used in hosted environments (such as a JupyterHub) to expose non-Jupyter interactive frontends or APIs to the user.
jupyter-server-proxy did not check user authentication appropriately when proxying websockets, allowing unauthenticated access to anyone who had network access to the Jupyter server endpoint.
Impact
This vulnerability can allow unauthenticated remote access to any websocket endpoint set up to be accessible via jupyter-server-proxy. In many cases (such as when exposing RStudio via jupyter-rsession-proxy or a remote Linux Desktop / VNC via jupyter-remote-desktop-proxy), this leads to remote unauthenticated arbitrary code execution, due to how they use websockets. The websocket endpoints exposed by jupyterserver itself is not affected. Projects that do not rely on websockets are also not affected.
Remediation
Upgrade jupyter-server-proxy to a patched version and restart any running Jupyter server.
You may not be installing jupyter-server-proxy directly, but have it be pulled in as a dependency (partial list of dependent packages) - so you may be vulnerable even if you aren't directly depending on jupyter-server-proxy.
For JupyterHub admins of [TLJH] installations
<details><summary>Expand to read more</summary>
To secure a tljh deployment's user servers, first check if jupyter-server-proxy is installed in the user environment with a vulnerable version. If it is, patch the vulnerability and consider terminating currently running user servers.
[tljh]: https://tljh.jupyter.org
1. Check for vulnerability
As an JupyterHub admin from a terminal in a started user server, you can do:
bash sudo -E python3 -c ' try: import jupyterserverproxy isvulnerable = not hasattr(jupyterserverproxy, "version") except: isvulnerable = False if isvulnerable: print("WARNING: jupyter-server-proxy is vulnerable to GHSA-w3vc-fx9p-wp4v, see https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v.") else: print("INFO: not vulnerable to GHSA-w3vc-fx9p-wp4v") '
Alternatively as a root user on the server where tljh is installed, you can do:
bash sudo PATH=/opt/tljh/user/bin:${PATH} python3 -c ' try: import jupyterserverproxy isvulnerable = not hasattr(jupyterserverproxy, "version") except: isvulnerable = False if isvulnerable: print("WARNING: jupyter-server-proxy is vulnerable to GHSA-w3vc-fx9p-wp4v, see https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v.") else: print("INFO: not vulnerable to GHSA-w3vc-fx9p-wp4v") '
2. Patch detected vulnerability
As an JupyterHub admin from a terminal in a started user server, you can do:
bash sudo -E pip install "jupyter-server-proxy>=3.2.3,!=4.0.0,!=4.1.0"
Alternatively as a root user on the server where tljh is installed, you can do:
bash sudo PATH=/opt/tljh/user/bin:${PATH} pip install "jupyter-server-proxy>=3.2.3,!=4.0.0,!=4.1.0"
3. Consider terminating currently running user servers
User servers that started before the patch was applied are still vulnerable. To ensure they aren't vulnerable any more you could forcefully terminate their servers via the JupyterHub web interface at https://<your domain>/hub/admin.
</details>
For JupyterHub admins of [Z2JH] installations
<details><summary>Expand to read more</summary>
To secure your z2jh deployment's user servers, first consider if one or more user environments is or may be vulnerable, then ensure new user servers' aren't started with the vulnerability, and finally consider terminating currently running user servers. The steps below guide you to do so.
[z2jh]: https://z2jh.jupyter.org
1. Check for vulnerabilities
Consider all docker images that user servers' environment may be based on. If your deployment expose a fixed set of images, you may be able to update them to non-vulnerable versions.
To check if an individual docker image is vulnerable, use a command like:
bash CHECKIMAGE=jupyter/base-notebook:2023-10-20 docker run --rm $CHECKIMAGE python3 -c ' try: import jupyterserverproxy isvulnerable = not hasattr(jupyterserverproxy, "version") except: isvulnerable = False if isvulnerable: print("WARNING: jupyter-server-proxy is vulnerable to GHSA-w3vc-fx9p-wp4v, see https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v.") else: print("INFO: not vulnerable to GHSA-w3vc-fx9p-wp4v") '
Note that if you reference an image with a mutable tag, such as quay.io/jupyter/pangeo-notebook:master, you should ensure a new version is used by configuring the image pull policy so that an older vulnerable version isn't kept being used because it was already available on a Kubernetes node.
yaml singleuser: image: name: quay.io/jupyter/pangeo-notebook tag: master # pullPolicy (a.k.a. imagePullPolicy in k8s specification) should be # declared to Always if you make use of mutable tags pullPolicy: Always
2. Patch vulnerabilities dynamically
If your z2jh deployment still may start vulnerable images for users, you could mount a script that checks and patches the vulnerability before the jupyter server starts.
Below is JupyterHub Helm chart configuration that relies on [singleuser.extraFiles] and [singleuser.cmd] to mount a script we use as an entrypoint to dynamically check and patch the vulnerability before jupyter server is started.
Unless you change it, the script will attempt to upgrade jupyter-server-proxy to a non-vulnerable version if needed, and error if it needs to and fails. You can adjust this behavior by adjusting the constants UPGRADEIFVULNERABLE and ERRORIFVULNERABLE inside the script.
[singleuser.extraFiles]: https://z2jh.jupyter.org/en/stable/resources/reference.html#singleuser-extrafiles [singleuser.cmd]: https://z2jh.jupyter.org/en/stable/resources/reference.html#singleuser-cmd
yaml singleuser: cmd: - /mnt/ghsa-w3vc-fx9p-wp4v/check-patch-run - jupyterhub-singleuser extraFiles: ghsa-w3vc-fx9p-wp4v-check-patch-run: mountPath: /mnt/ghsa-w3vc-fx9p-wp4v/check-patch-run mode: 0755 stringData: | #!/usr/bin/env python3 """ This script is designed to check for and conditionally patch GHSA-w3vc-fx9p-wp4v in user servers started by a JupyterHub. The script will execute any command passed via arguments if provided, allowing it to wrap a user server startup call to jupyterhub-singleuser for example.
Use and function of this script can be further discussed in https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues/3360.
Script adjustments: - UPGRADEIFVULNERABLE - ERRORIFVULNERABLE
Script patching assumptions: - script is run before the jupyter server starts - pip is available - pip has sufficient filesystem permissions to upgrade jupyter-server-proxy
Read more at https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v. """
import os import subprocess import sys
# adjust these to meet vulnerability mitigation needs UPGRADEIFVULNERABLE = True ERRORIFVULNERABLE = True
def checkvuln(): """ Checks for the vulnerability by looking to see if version is available as it coincides with the patched versions (3.2.3 and 4.1.1). """ try: import jupyterserverproxy
return False if hasattr(jupyterserverproxy, "version") else True except: return False
def getversionspecifier(): """ Returns a pip version specifier for use with --no-deps meant to do as little as possible besides patching the vulnerability and remaining functional. """ old = ["jupyter-server-proxy>=3.2.3,<4"] new = ["jupyter-server-proxy>=4.1.1,<5", "simpervisor>=1,<2"]
try: if sys.versioninfo < (3, 8): return old
from importlib.metadata import version
jspversion = version("jupyter-server-proxy") if int(jspversion.split(".")[0]) < 4: return old except: pass return new
def patchvuln(): """ Attempts to patch the vulnerability by upgrading jupyter-server-proxy using pip. Returns True if the patch is applied successfully, otherwise False. """ # attempt upgrade via pip, takes ~4 seconds proc = subprocess.run( [sys.executable, "-m", "pip", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) pipavailable = proc.returncode == 0 if pipavailable: proc = subprocess.run( [sys.executable, "-m", "pip", "install", "--no-deps"] + getversionspecifier() ) if proc.returncode == 0: return True return False
def main(): if checkvuln(): warningorerror = ( "ERROR" if ERRORIFVULNERABLE and not UPGRADEIFVULNERABLE else "WARNING" ) print( f"{warningorerror}: jupyter-server-proxy is vulnerable to GHSA-w3vc-fx9p-wp4v, see " "https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v.", flush=True, ) if warningorerror == "ERROR": sys.exit(1)
if UPGRADEIFVULNERABLE: print( "INFO: Attempting to upgrade jupyter-server-proxy using pip...", flush=True, ) if patchvuln(): print( "INFO: Attempt to upgrade jupyter-server-proxy succeeded!", flush=True, ) else: warningorerror = "ERROR" if ERRORIFVULNERABLE else "WARNING" print( f"{warningorerror}: Attempt to upgrade jupyter-server-proxy failed!", flush=True, ) if warningorerror == "ERROR": sys.exit(1)
if len(sys.argv) >= 2: print("INFO: Executing provided command", flush=True) os.execvp(sys.argv[1], sys.argv[1:]) else: print("INFO: No command to execute provided", flush=True)
main()
3. Consider terminating currently running user servers
User servers that started before the patch was applied are still vulnerable. To ensure they aren't vulnerable any more you could forcefully terminate their servers via the JupyterHub web interface at https://<your domain>/hub/admin.
</details>
Simple Reproduction
<details><summary>Expand to read more</summary>
Setup application to proxy
Make a trivial tornado app that has both websocket and regular HTTP endpoints.
python from tornado import websocket, web, ioloop
class EchoWebSocket(websocket.WebSocketHandler): def open(self): print("WebSocket opened")
def onmessage(self, message): self.writemessage(u"You said: " + message)
def onclose(self): print("WebSocket closed")
class HiHandler(web.RequestHandler): def get(self): self.write("Hi")
app = web.Application([ (r'/ws', EchoWebSocket), (r'/hi', HiHandler) ])
if name == 'main': app.listen(9500) ioloop.IOLoop.instance().start()
Setup a clean environment with jupyter-server-proxy and start a jupyter server instance
We don't need jupyterlab or anything else here, just jupyter-server-proxy would do.
bash python -m venv clean-env/ source clean-env/bin/activate pip install jupyter-server-proxy jupyter server
Verify HTTP requests require authentication
bash curl -L http://127.0.0.1:8888/proxy/9500/hi
This does not return the Hi response, as expected. Instead, you get the HTML response asking for a token.
This is secure as intended.
Verify websocket requests doesn't authentication
The example makes use of websocat to test websockets. You can use any other tool you are familiar with too.
bash websocat ws://localhost:8888/proxy/9500/ws
At the terminal, type 'Just testing' and press Enter. You'll get You said: Just testing without any authentication required.
</details>
Impact
There is a reflected cross-site scripting (XSS) issue in jupyter-server-proxy[1]. The /proxy endpoint accepts a host path segment in the format /proxy/<host>. When this endpoint is called with an invalid host value, jupyter-server-proxy replies with a response that includes the value of host, without sanitization [2]. A third-party actor can leverage this by sending a phishing link with an invalid host value containing custom JavaScript to a user. When the user clicks this phishing link, the browser renders the response of GET /proxy/<host>, which runs the custom JavaScript contained in host set by the actor. As any arbitrary JavaScript can be run after the user clicks on a phishing link, this issue permits extensive access to the user's JupyterLab instance for an actor. This issue exists in the latest release of jupyter-server-proxy, currently v4.1.2. Impacted versions: >=3.0.0,<=4.1.2
Patches
The patches are included in ==4.2.0 and ==3.2.4.
Workarounds
Server operators who are unable to upgrade can disable the jupyter-server-proxy extension with:
jupyter server extension disable jupyter-server-proxy
References
[1] : https://github.com/jupyterhub/jupyter-server-proxy/ [2] : https://github.com/jupyterhub/jupyter-server-proxy/blob/62a290f08750f7ae55a0c29ca339c9a39a7b2a7b/jupyterserverproxy/handlers.py#L328
Affects: Notebook and Lab between 6.4.0?(potentially earlier) and 6.4.11 (currently latest). Jupyter Server <=1.16.0. If I am correct about the responsible code it will affect Jupyter-Server 1.17.0 and 2.0.0a0 as well. Description: If notebook server is started with a value of rootdir that contains the starting user's home directory, then the underlying REST API can be used to leak the access token assigned at start time by guessing/brute forcing the PID of the jupyter server. While this requires an authenticated user session, this url can be used from an xss payload (as in CVE-2021-32798) or from a hooked or otherwise compromised browser to leak this access token to a malicious third party. This token can be used along with the REST API to interact with Jupyter services/notebooks such as modifying or overwriting critical files, such as .bashrc or .ssh/authorizedkeys, allowing a malicious user to read potentially sensitive data and possibly gain control of the impacted system.
Summary
Jupyter Server on Windows has a vulnerability that lets unauthenticated attackers leak the NTLMv2 password hash of the Windows user running the Jupyter server. An attacker can crack this password to gain access to the Windows machine hosting the Jupyter server, or access other network-accessible machines or 3rd party services using that credential. Or an attacker perform an NTLM relay attack without cracking the credential to gain access to other network-accessible machines.
The Jupyter Server provides the backend (i.e. the core services, APIs, and REST endpoints) for Jupyter web applications. Prior to version 1.15.4, unauthorized actors can access sensitive information from server logs. Anytime a 5xx error is triggered, the auth cookie and other header values are recorded in Jupyter Server logs by default. Considering these logs do not require root access, an attacker can monitor these logs, steal sensitive auth/cookie information, and gain access to the Jupyter server. Jupyter Server version 1.15.4 contains a patch for this issue. There are currently no known workarounds.
Impact
What kind of vulnerability is it? Server-Side Request Forgery ( SSRF )
Who is impacted? Any user deploying Jupyter Server or Notebook with jupyter-proxy-server extension enabled.
A lack of input validation allowed authenticated clients to proxy requests to other hosts, bypassing the allowedhosts check. Because authentication is required, which already grants permissions to make the same requests via kernel or terminal execution, this is considered low to moderate severity.
Patches
Has the problem been patched? What versions should users upgrade to?
Upgrade to 3.2.1, or apply the patch https://github.com/jupyterhub/jupyter-server-proxy/compare/v3.2.0...v3.2.1.patch
For more information
If you have any questions or comments about this advisory:
Open a topic on our forum Email the Jupyter security team at security@ipython.org
Impact What kind of vulnerability is it? Who is impacted?
Open redirect vulnerability - a maliciously crafted link to a jupyter server could redirect the browser to a different website.
All jupyter servers running without a baseurl prefix are technically affected, however, these maliciously crafted links can only be reasonably made for known jupyter server hosts. A link to your jupyter server may appear safe, but ultimately redirect to a spoofed server on the public internet. This same vulnerability was patched in upstream notebook v5.7.8.
Patches
Has the problem been patched? What versions should users upgrade to?
Patched in jupyterserver 1.1.1. If upgrade is not available, a workaround can be to run your server on a url prefix:
jupyter server --ServerApp.baseurl=/jupyter/
References
OWASP page on open redirects
For more information
If you have any questions or comments about this advisory, or vulnerabilities to report, please email our security list security@ipython.org.
Credit: Yaniv Nizry from CxSCA group at Checkmarx
Impact
Improper cross-site credential checks on /files/ URLs could allow exposure of certain file contents, or accessing files when opening untrusted files via "Open image in new tab".
Patches
Jupyter Server 2.7.2
Workarounds
Use lower performance --ContentsManager.fileshandlerclass=jupyterserver.files.handlers.FilesHandler, which implements the correct checks.
References
Upstream patch for CVE-2019-9644 was not applied completely, leaving part of the vulnerability open.
Vulnerability reported by Tim Coen via the bug bounty program sponsored by the European Commission and hosted on the Intigriti platform.
Impact
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.
Patches
Upgrade to Jupyter Server 2.7.2
Workarounds
None.
References
Vulnerability reported by user davwwwx via the bug bounty program sponsored by the European Commission and hosted on the Intigriti platform.
- https://blog.xss.am/2023/08/cve-2023-39968-jupyter-token-leak/
Impact What kind of vulnerability is it? Who is impacted?
Open redirect vulnerability - a maliciously crafted link to a jupyter server could redirect the browser to a different website.
All jupyter servers are technically affected, however, these maliciously crafted links can only be reasonably made for known jupyter server hosts. A link to your jupyter server may appear safe, but ultimately redirect to a spoofed server on the public internet.
This originated in jupyter/notebook: https://github.com/jupyter/notebook/security/advisories/GHSA-c7vm-f5p4-8fqh
Patches
Has the problem been patched? What versions should users upgrade to?
jupyterserver 1.0.6
References
OWASP page on open redirects
For more information
If you have any questions or comments about this advisory, or vulnerabilities to report, please email our security list security@ipython.org.
Credit: zhuonan li of Alibaba Application Security Team
Impact
Unhandled errors in API requests include traceback information, which can include path information. There is no known mechanism by which to trigger these errors without authentication, so the paths revealed are not considered particularly sensitive, given that the requesting user has arbitrary execution permissions already in the same environment.
Patches
jupyter-server PATCHEDVERSION no longer includes traceback information in JSON error responses. For compatibility, the traceback field is present, but always empty.
Workarounds
None