CVE-2024-28179: Jupyter server proxy Jupyter Server Proxy's Websocket Proxying Vulnerability

CRITICAL
Advisory Identifier: GHSA-w3vc-fx9p-wp4v
CVSS 9.0
CWE-306
Published
Advisory Summary

CVE-2024-28179 is a CRITICAL (CVSS 9.0) jupyter server proxy's websocket proxying vulnerability (CWE-306: Missing Authentication for Critical Function) affecting Jupyter server proxy libraries in the PyPI ecosystem. The issue involves jupyter server proxy's websocket proxying does not require authentication. Remediation is available by upgrading affected packages to version 4.1.1, 3.2.3.

CVE-2024-28179 at a Glance

CVE Identifier CVE-2024-28179
GHSA Identifier GHSA-w3vc-fx9p-wp4v
Severity CRITICAL
CVSS v3.1 Base Score 9.0
CWE Classification CWE-306: Missing Authentication for Critical Function
Affected Product Jupyter server proxy
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 9.0

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2024-28179

To remediate this vulnerability, upgrade all affected Jupyter server proxy dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

jupyter-server-proxy fixed: 4.1.1
pip install jupyter-server-proxy>=4.1.1
jupyter-server-proxy fixed: 3.2.3
pip install jupyter-server-proxy>=3.2.3
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show jupyter-server-proxy

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2024-28179, inspect your installed packages using pip:

pip show jupyter-server-proxy

Analyze the terminal output to locate any version of Jupyter server proxy in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.1.1), your application is affected.

Note: The pip show jupyter-server-proxy command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-306: Missing Authentication for Critical Function, where the product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources. In Jupyter server proxy, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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](https://www.wheelodex.org/projects/jupyter-server-proxy/rdepends/)) also use `jupyter-server-proxy` to expose other popular interactive applications (such as [RStudio](https://github.com/jupyterhub/jupyter-rsession-proxy), [Linux Desktop via VNC](https://github.com/jupyterhub/jupyter-remote-desktop-proxy), [Code Server](https://github.com/betatim/vscode-binder), [Panel](https://github.com/holoviz/jupyter-panel-proxy), 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`](https://github.com/jupyterhub/jupyter-rsession-proxy) or a remote Linux Desktop / VNC via [`jupyter-remote-desktop-proxy`](https://github.com/jupyterhub/jupyter-remote-desktop-proxy)), this leads to **remote unauthenticated arbitrary code execution**, due to how they use websockets. The websocket endpoints exposed by `jupyter_server` 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](https://www.wheelodex.org/projects/jupyter-server-proxy/rdepends/)) - 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 jupyter_server_proxy is_vulnerable = not hasattr(jupyter_server_proxy, "__version__") except: is_vulnerable = False if is_vulnerable: 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 jupyter_server_proxy is_vulnerable = not hasattr(jupyter_server_proxy, "__version__") except: is_vulnerable = False if is_vulnerable: 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 CHECK_IMAGE=jupyter/base-notebook:2023-10-20 docker run --rm $CHECK_IMAGE python3 -c ' try: import jupyter_server_proxy is_vulnerable = not hasattr(jupyter_server_proxy, "__version__") except: is_vulnerable = False if is_vulnerable: 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 `UPGRADE_IF_VULNERABLE` and `ERROR_IF_VULNERABLE` 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: - UPGRADE_IF_VULNERABLE - ERROR_IF_VULNERABLE 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 UPGRADE_IF_VULNERABLE = True ERROR_IF_VULNERABLE = True def check_vuln(): """ 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 jupyter_server_proxy return False if hasattr(jupyter_server_proxy, "__version__") else True except: return False def get_version_specifier(): """ 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.version_info < (3, 8): return old from importlib.metadata import version jsp_version = version("jupyter-server-proxy") if int(jsp_version.split(".")[0]) < 4: return old except: pass return new def patch_vuln(): """ 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, ) pip_available = proc.returncode == 0 if pip_available: proc = subprocess.run( [sys.executable, "-m", "pip", "install", "--no-deps"] + get_version_specifier() ) if proc.returncode == 0: return True return False def main(): if check_vuln(): warning_or_error = ( "ERROR" if ERROR_IF_VULNERABLE and not UPGRADE_IF_VULNERABLE else "WARNING" ) print( f"{warning_or_error}: 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 warning_or_error == "ERROR": sys.exit(1) if UPGRADE_IF_VULNERABLE: print( "INFO: Attempting to upgrade jupyter-server-proxy using pip...", flush=True, ) if patch_vuln(): print( "INFO: Attempt to upgrade jupyter-server-proxy succeeded!", flush=True, ) else: warning_or_error = "ERROR" if ERROR_IF_VULNERABLE else "WARNING" print( f"{warning_or_error}: Attempt to upgrade jupyter-server-proxy failed!", flush=True, ) if warning_or_error == "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 on_message(self, message): self.write_message(u"You said: " + message) def on_close(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](https://github.com/vi/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>

CVE-2024-28179 Affected Jupyter server proxy Versions

2 component(s)
Fixed Versions:
4.1.13.2.3
Product / Package Ecosystem Affected Versions Fixed Version
jupyter-server-proxy
PyPI
≥ 4.0.0
< 4.1.1
4.1.1
jupyter-server-proxy
PyPI
< 3.2.3
3.2.3

References

Frequently Asked Questions

What is CVE-2024-28179?
CVE-2024-28179 is a security vulnerability in Jupyter server proxy involving Jupyter Server Proxy's Websocket Proxying does not require authentication under CWE-306: Missing Authentication for Critical Function.
Which Jupyter server proxy versions are affected?
Affected versions include releases prior to 4.1.1, 3.2.3 across affected packages: jupyter-server-proxy.
What version fixes CVE-2024-28179?
Upgrading to 4.1.1, 3.2.3 (or the latest patched release for your respective branch) resolves the vulnerability.
Is jupyter-server-proxy affected?
Yes, jupyter-server-proxy is affected in releases prior to 4.1.1, 3.2.3. Upgrade your dependency to 4.1.1, 3.2.3 or later.
What is the CVSS score of CVE-2024-28179?
CVE-2024-28179 has a CVSS v3.1 base score of 9.0 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H.
What CWE is associated with CVE-2024-28179?
CVE-2024-28179 is classified under CWE-306: Missing Authentication for Critical Function (The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.).
How can I check PyPI dependencies for CVE-2024-28179?
Run the dependency tree command: 'pip show jupyter-server-proxy'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-w3vc-fx9p-wp4v",
  "modified" : "2025-02-21T22:32:30Z",
  "published" : "2024-03-20T15:22:02Z",
  "aliases" : [ "CVE-2024-28179" ],
  "summary" : "Jupyter Server Proxy's Websocket Proxying does not require authentication",
  "details" : "## Summary\n\n`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](https://www.wheelodex.org/projects/jupyter-server-proxy/rdepends/)) also use `jupyter-server-proxy` to expose other popular interactive applications (such as [RStudio](https://github.com/jupyterhub/jupyter-rsession-proxy), [Linux Desktop via VNC](https://github.com/jupyterhub/jupyter-remote-desktop-proxy), [Code Server](https://github.com/betatim/vscode-binder), [Panel](https://github.com/holoviz/jupyter-panel-proxy), 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.\n\n`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.\n\n## Impact\n\nThis 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`](https://github.com/jupyterhub/jupyter-rsession-proxy) or a remote Linux Desktop / VNC via [`jupyter-remote-desktop-proxy`](https://github.com/jupyterhub/jupyter-remote-desktop-proxy)), this leads to **remote unauthenticated arbitrary code execution**, due to how they use websockets. The websocket endpoints exposed by `jupyter_server` itself is not affected. Projects that do not rely on websockets are also not affected.\n\n## Remediation\n\nUpgrade `jupyter-server-proxy` to a patched version and restart any running Jupyter server.\n\nYou may not be installing `jupyter-server-proxy` directly, but have it be pulled in as a dependency ([partial list of dependent packages](https://www.wheelodex.org/projects/jupyter-server-proxy/rdepends/)) - so you may be vulnerable even if you aren't directly depending on `jupyter-server-proxy`.\n\n### For JupyterHub admins of [TLJH] installations\n\n<details><summary>Expand to read more</summary>\n\nTo 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.\n\n[tljh]: https://tljh.jupyter.org\n\n#### 1. Check for vulnerability\n\nAs an JupyterHub admin from a terminal in a started user server, you can do:\n\n```bash\nsudo -E python3 -c '\ntry:\n    import jupyter_server_proxy\n    is_vulnerable = not hasattr(jupyter_server_proxy, \"__version__\")\nexcept:\n    is_vulnerable = False\nif is_vulnerable:\n    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.\")\nelse:\n    print(\"INFO: not vulnerable to GHSA-w3vc-fx9p-wp4v\")\n'\n```\n\nAlternatively as a root user on the server where tljh is installed, you can do:\n\n```bash\nsudo PATH=/opt/tljh/user/bin:${PATH} python3 -c '\ntry:\n    import jupyter_server_proxy\n    is_vulnerable = not hasattr(jupyter_server_proxy, \"__version__\")\nexcept:\n    is_vulnerable = False\nif is_vulnerable:\n    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.\")\nelse:\n    print(\"INFO: not vulnerable to GHSA-w3vc-fx9p-wp4v\")\n'\n```\n\n#### 2. Patch detected vulnerability\n\nAs an JupyterHub admin from a terminal in a started user server, you can do:\n\n```bash\nsudo -E pip install \"jupyter-server-proxy>=3.2.3,!=4.0.0,!=4.1.0\"\n```\n\nAlternatively as a root user on the server where tljh is installed, you can do:\n\n```bash\nsudo PATH=/opt/tljh/user/bin:${PATH} pip install \"jupyter-server-proxy>=3.2.3,!=4.0.0,!=4.1.0\"\n```\n\n#### 3. Consider terminating currently running user servers\n\nUser 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`.\n\n</details>\n\n### For JupyterHub admins of [Z2JH] installations\n\n<details><summary>Expand to read more</summary>\n\nTo 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.\n\n[z2jh]: https://z2jh.jupyter.org\n\n#### 1. Check for vulnerabilities\n\nConsider 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.\n\nTo check if an individual docker image is vulnerable, use a command like:\n\n```bash\nCHECK_IMAGE=jupyter/base-notebook:2023-10-20\ndocker run --rm $CHECK_IMAGE python3 -c '\ntry:\n    import jupyter_server_proxy\n    is_vulnerable = not hasattr(jupyter_server_proxy, \"__version__\")\nexcept:\n    is_vulnerable = False\nif is_vulnerable:\n    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.\")\nelse:\n    print(\"INFO: not vulnerable to GHSA-w3vc-fx9p-wp4v\")\n'\n```\n\nNote 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.\n\n```yaml\nsingleuser:\n  image:\n    name: quay.io/jupyter/pangeo-notebook\n    tag: master\n    # pullPolicy (a.k.a. imagePullPolicy in k8s specification) should be\n    # declared to Always if you make use of mutable tags\n    pullPolicy: Always\n```\n\n#### 2. Patch vulnerabilities dynamically\n\nIf 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.\n\nBelow 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.\n\nUnless 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 `UPGRADE_IF_VULNERABLE` and `ERROR_IF_VULNERABLE` inside the script.\n\n[`singleuser.extraFiles`]: https://z2jh.jupyter.org/en/stable/resources/reference.html#singleuser-extrafiles\n[`singleuser.cmd`]: https://z2jh.jupyter.org/en/stable/resources/reference.html#singleuser-cmd\n\n```yaml\nsingleuser:\n  cmd:\n    - /mnt/ghsa-w3vc-fx9p-wp4v/check-patch-run\n    - jupyterhub-singleuser\n  extraFiles:\n    ghsa-w3vc-fx9p-wp4v-check-patch-run:\n      mountPath: /mnt/ghsa-w3vc-fx9p-wp4v/check-patch-run\n      mode: 0755\n      stringData: |\n        #!/usr/bin/env python3\n        \"\"\"\n        This script is designed to check for and conditionally patch GHSA-w3vc-fx9p-wp4v\n        in user servers started by a JupyterHub. The script will execute any command\n        passed via arguments if provided, allowing it to wrap a user server startup call\n        to `jupyterhub-singleuser` for example.\n\n        Use and function of this script can be further discussed in\n        https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues/3360.\n\n        Script adjustments:\n        - UPGRADE_IF_VULNERABLE\n        - ERROR_IF_VULNERABLE\n\n        Script patching assumptions:\n        - script is run before the jupyter server starts\n        - pip is available\n        - pip has sufficient filesystem permissions to upgrade jupyter-server-proxy\n\n        Read more at https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v.\n        \"\"\"\n\n        import os\n        import subprocess\n        import sys\n\n        # adjust these to meet vulnerability mitigation needs\n        UPGRADE_IF_VULNERABLE = True\n        ERROR_IF_VULNERABLE = True\n\n\n        def check_vuln():\n            \"\"\"\n            Checks for the vulnerability by looking to see if __version__ is available\n            as it coincides with the patched versions (3.2.3 and 4.1.1).\n            \"\"\"\n            try:\n                import jupyter_server_proxy\n\n                return False if hasattr(jupyter_server_proxy, \"__version__\") else True\n            except:\n                return False\n\n\n        def get_version_specifier():\n            \"\"\"\n            Returns a pip version specifier for use with `--no-deps` meant to do as\n            little as possible besides patching the vulnerability and remaining\n            functional.\n            \"\"\"\n            old = [\"jupyter-server-proxy>=3.2.3,<4\"]\n            new = [\"jupyter-server-proxy>=4.1.1,<5\", \"simpervisor>=1,<2\"]\n\n            try:\n                if sys.version_info < (3, 8):\n                    return old\n\n                from importlib.metadata import version\n\n                jsp_version = version(\"jupyter-server-proxy\")\n                if int(jsp_version.split(\".\")[0]) < 4:\n                    return old\n            except:\n                pass\n            return new\n\n\n        def patch_vuln():\n            \"\"\"\n            Attempts to patch the vulnerability by upgrading jupyter-server-proxy using\n            pip. Returns True if the patch is applied successfully, otherwise False.\n            \"\"\"\n            # attempt upgrade via pip, takes ~4 seconds\n            proc = subprocess.run(\n                [sys.executable, \"-m\", \"pip\", \"--version\"],\n                stdout=subprocess.DEVNULL,\n                stderr=subprocess.DEVNULL,\n            )\n            pip_available = proc.returncode == 0\n            if pip_available:\n                proc = subprocess.run(\n                    [sys.executable, \"-m\", \"pip\", \"install\", \"--no-deps\"]\n                    + get_version_specifier()\n                )\n                if proc.returncode == 0:\n                    return True\n            return False\n\n\n        def main():\n            if check_vuln():\n                warning_or_error = (\n                    \"ERROR\" if ERROR_IF_VULNERABLE and not UPGRADE_IF_VULNERABLE else \"WARNING\"\n                )\n                print(\n                    f\"{warning_or_error}: jupyter-server-proxy __is vulnerable__ to GHSA-w3vc-fx9p-wp4v, see \"\n                    \"https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v.\",\n                    flush=True,\n                )\n                if warning_or_error == \"ERROR\":\n                    sys.exit(1)\n\n                if UPGRADE_IF_VULNERABLE:\n                    print(\n                        \"INFO: Attempting to upgrade jupyter-server-proxy using pip...\",\n                        flush=True,\n                    )\n                    if patch_vuln():\n                        print(\n                            \"INFO: Attempt to upgrade jupyter-server-proxy succeeded!\",\n                            flush=True,\n                        )\n                    else:\n                        warning_or_error = \"ERROR\" if ERROR_IF_VULNERABLE else \"WARNING\"\n                        print(\n                            f\"{warning_or_error}: Attempt to upgrade jupyter-server-proxy failed!\",\n                            flush=True,\n                        )\n                        if warning_or_error == \"ERROR\":\n                            sys.exit(1)\n\n            if len(sys.argv) >= 2:\n                print(\"INFO: Executing provided command\", flush=True)\n                os.execvp(sys.argv[1], sys.argv[1:])\n            else:\n                print(\"INFO: No command to execute provided\", flush=True)\n\n\n        main()\n```\n\n#### 3. Consider terminating currently running user servers\n\nUser 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`.\n\n</details>\n\n## Simple Reproduction\n\n<details><summary>Expand to read more</summary>\n\n### Setup application to proxy\n\nMake a trivial tornado app that has both websocket and regular HTTP endpoints.\n\n```python\nfrom tornado import websocket, web, ioloop\n\nclass EchoWebSocket(websocket.WebSocketHandler):\n    def open(self):\n        print(\"WebSocket opened\")\n\n    def on_message(self, message):\n        self.write_message(u\"You said: \" + message)\n\n    def on_close(self):\n        print(\"WebSocket closed\")\n\nclass HiHandler(web.RequestHandler):\n    def get(self):\n        self.write(\"Hi\")\n\napp = web.Application([\n    (r'/ws', EchoWebSocket),\n    (r'/hi', HiHandler)\n])\n\nif __name__ == '__main__':\n    app.listen(9500)\n    ioloop.IOLoop.instance().start()\n```\n\n### Setup a clean environment with `jupyter-server-proxy` and start a `jupyter server` instance\n\nWe don't need jupyterlab or anything else here, just `jupyter-server-proxy` would do.\n\n```bash\npython -m venv clean-env/\nsource clean-env/bin/activate\npip install jupyter-server-proxy\njupyter server\n```\n\n### Verify HTTP requests require authentication\n\n```bash\ncurl -L http://127.0.0.1:8888/proxy/9500/hi\n```\n\nThis does *not* return the `Hi` response, as expected. Instead, you get the HTML response asking for a token.\n\nThis is secure as intended.\n\n### Verify websocket requests doesn't authentication\n\nThe example makes use of [websocat](https://github.com/vi/websocat) to test websockets. You can use any other tool you are familiar with too.\n\n```bash\nwebsocat ws://localhost:8888/proxy/9500/ws\n```\n\nAt the terminal, type 'Just testing' and press Enter. You'll get `You said: Just testing` without any authentication required.\n\n</details>",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "jupyter-server-proxy"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "4.0.0"
      }, {
        "fixed" : "4.1.1"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "jupyter-server-proxy"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.2.3"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-w3vc-fx9p-wp4v"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2024-28179"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jupyterhub/jupyter-server-proxy/commit/764e499f61a87641916a7a427d4c4b1ac3f321a9"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jupyterhub/jupyter-server-proxy/commit/bead903b7c0354b6efd8b4cde94b89afab653e03"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/jupyterhub/jupyter-server-proxy"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jupyterhub/jupyter-server-proxy/blob/9b624c4d9507176334b46a85d94a4aa3bcd29bed/jupyter_server_proxy/handlers.py#L433"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/pypa/advisory-database/tree/main/vulns/jupyter-server-proxy/PYSEC-2024-234.yaml"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-306" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2024-03-20T15:22:02Z",
    "nvd_published_at" : "2024-03-20T20:15:08Z"
  }
}