Marimo(一个用于数据科学和 AI/ML 的开源 Python 笔记本)中存在一个未认证远程代码执行漏洞。终端 WebSocket 端点(/terminal/ws)完全跳过了身份验证检查,而相邻的笔记本端点(/ws)则正确执行了该检查。未认证的攻击者可以连接到 /terminal/ws,在零凭据的情况下获得主机系统上的完整交互式 PTY shell。
该漏洞在披露后 10 小时内即被在野利用。攻击者在不到 3 分钟内窃取了 AWS 凭据。
影响 Marimo <= 0.20.4。已在 Marimo 0.23.0 中修复。
| 字段 | 详情 |
|---|---|
| CVE ID | CVE-2026-39987 |
| 厂商 | Marimo Project |
| 产品 | Marimo(Python 笔记本) |
| 受影响版本 | <= 0.20.4 |
| CVSS v3.1 | 9.3(严重) |
| CWE | CWE-306 — 关键功能缺少身份验证 |
| 攻击向量 | 网络 |
| 身份验证 | 无需认证 |
| 用户交互 | 无 |
| 利用成熟度 | 已在野外被积极利用 |
| 利用时间 | 披露后约 10 小时 |
| 修复版本 | Marimo 0.23.0 |
Marimo 是一个开源的反应式 Python 笔记本,旨在作为 Jupyter 的现代替代品。它专为数据科学、AI/ML 实验和交互式数据分析而构建。其关键特性包括自动依赖跟踪、可复现执行,以及相比传统笔记本更简洁的开发者体验。
Marimo 正在 Python 和 AI/ML 社区中迅速获得关注,尤其是那些希望获得比 Jupyter 更结构化笔记本工作流的从业者。
与所有笔记本环境一样,Marimo 实例通常可以访问敏感资源:云凭据(AWS、GCP、Azure)、数据库连接字符串、AI 服务的 API 密钥(OpenAI、Anthropic 等)以及内部网络访问权限。与传统 Web 应用不同,笔记本设计用于执行任意代码。这正是它们的核心用途。
这种组合使得笔记本环境中任何身份验证绕过都极具破坏性。``` Typical Marimo Deployment:
┌──────────────┐ ┌────────────────────────────────┐ │ │ HTTP │ Marimo Server │ │ Browser │────────>│ │ │ (User) │ │ ┌──────────────────────────┐ │ │ │<────────│ │ /ws (Notebook) │ │ └──────────────┘ WS │ │ ✅ validate_auth() │ │ │ └──────────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ /terminal/ws │ │ │ │ ❌ NO AUTH CHECK │ │ │ └──────────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ Python Environment │ │ │ │ .env files │ │ │ │ AWS credentials │ │ │ │ API keys │ │ │ └──────────────────────────┘ │ └────────────────────────────────┘
---
## 漏洞深度剖析
### 两个 WebSocket 端点
Marimo 的服务器为不同功能实现了多个 WebSocket 端点。两个主要端点之间的关键区别在于是否存在身份验证检查:```
Authentication Flow Comparison:
/ws (Notebook WebSocket):
┌─────────┐ ┌───────────────┐ ┌──────────┐ ┌───────────┐
│ Connect │───>│ validate_auth │───>│ Accept │───>│ Notebook │
└─────────┘ └───────┬───────┘ └──────────┘ └───────────┘
│
❌ Reject if
not authenticated
/terminal/ws (Terminal WebSocket):
┌─────────┐ ┌───────────────┐ ┌──────────┐ ┌───────────┐
│ Connect │───>│ Check mode & │───>│ Accept │───>│ PTY Shell │
└─────────┘ │ platform only │ └──────────┘ └───────────┘
└───────────────┘
⚠️ No auth check!
Anyone gets a shell!
笔记本端点(/ws)在接收 WebSocket 连接之前会正确调用 validate_auth() 来验证用户身份。这是预期的安全行为。
终端端点(/terminal/ws)仅检查服务器是否处于运行模式以及平台是否支持终端功能。它从不调用 validate_auth()。在通过这些基本检查后,它会接受连接并创建一个完整的 PTY(伪终端)会话。```python
async def websocket_connect(self, message): await self.validate_auth() # ✅ Checks authentication await self.accept() # ... notebook communication
async def websocket_connect(self, message): if not self.is_running_mode(): # Only checks mode await self.close() return if not self.is_platform_supported(): # Only checks platform await self.close() return await self.accept() # ❌ No auth! Anyone gets a shell # ... PTY shell creation
这是 **CWE-306:关键功能缺少身份验证**。服务器上最危险的端点(提供交互式 shell 的端点)完全没有身份验证。
### 攻击:3 分钟内获取 AWS 密钥
利用时间线展示了现代威胁行为者的行动速度有多快:```
┌──────────────────────────────────────────────────────────────┐
│ CVE-2026-39987 Timeline │
├──────────────────────────────────────────────────────────────┤
│ │
│ T+0h Advisory published │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ T+9h First exploit built from advisory │
│ T+10h Exploitation in the wild confirmed │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ T+10h 0m Attacker connects to /terminal/ws │
│ T+10h 1m Full PTY shell obtained │
│ T+10h 2m .env file located and read │
│ T+10h 3m AWS keys exfiltrated │
│ Total attack time: ~3 minutes │
└──────────────────────────────────────────────────────────────┘
攻击本身简单得令人发指:``` Step 1: Attacker opens WebSocket connection to /terminal/ws (No authentication needed, no special tools required)
Step 2: Server creates a PTY (pseudo-terminal) session Attacker now has an interactive shell
Step 3: Attacker runs commands: $ cat .env AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... DATABASE_URL=postgres://... OPENAI_API_KEY=sk-...
Step 4: Credentials exfiltrated Attacker now has cloud access, database access, and API keys for AI services
Total time: under 3 minutes Authentication required: none Tools required: any WebSocket client
无需开发漏洞利用。无需 shellcode。无需内存破坏。只需一个 WebSocket 客户端和一个缺失的认证检查。
---
## 影响分析
**对 Marimo 主机的直接影响:**
- 以 Marimo 进程的权限获得完整交互式 shell
- 可访问该进程可读取的所有文件(源代码、数据、凭据)
- 可访问包含 API 密钥和机密信息的环境变量
- 能够在主机系统上执行任意命令
**凭据泄露(主要攻击目标):**
- 来自 `.env` 文件或环境变量的 AWS 访问密钥和秘密密钥
- GCP/Azure 服务账户凭据
- 包含密码的数据库连接字符串
- OpenAI、Anthropic 及其他 AI 服务的 API 密钥
- SSH 密钥及其他认证材料
**下游影响(通过窃取的凭据):**
- 未经授权访问云基础设施(EC2、S3、Lambda 等)
- 从云存储和数据库窃取数据
- 资源滥用(加密货币挖矿、AI API 额度盗用)
- 横向移动进入云网络和本地网络
**风险放大因素:**
- 笔记本环境的设计目的就是执行任意代码(这正是其用途)
- 数据科学环境通常为训练任务配置了广泛的云访问权限
- 许多 Marimo 实例为协作和远程工作而暴露在互联网上
- 在研究/实验环境中,安全加固往往被事后才考虑
---
## 受影响版本
| 版本 | 状态 |
|---------|--------|
| Marimo 0.23.0+ | **已修复** |
| Marimo 0.20.5 至 0.22.x | **可能受影响**(介于公告范围与修复版本之间) |
| Marimo <= 0.20.4 | **受影响**(已确认范围) |
---
## 更宏观的视角:AI/ML 工具链遭受攻击
CVE-2026-39987 并非孤立事件。它是 2026 年 4 月出现的明显趋势的一部分:
| CVE | 产品 | 类型 | 状态 |
|:---|:---|:---|:---|
| **CVE-2026-39987** | Marimo | 预认证 RCE(WebSocket) | 10 小时内被利用 |
| **CVE-2026-33017** | Langflow | RCE | CISA KEV(3 月 26 日) |
| **CVE-2026-5059** | aws-mcp-server | 命令注入 RCE | 公开公告 |
| **TorchGeo** | TorchGeo | eval() RCE | 公开公告 |
一个月内,四款 AI/ML 开发工具遭到严重 RCE 漏洞攻击。AI/ML 开发流水线正成为新的影子 IT:部署时拥有广泛权限、安全监管极少,且存储着丰富的凭据。```
┌─────────────────────────────────────────────────┐
│ Why AI/ML Tools Are Prime Targets │
├─────────────────────────────────────────────────┤
│ │
│ 1. DESIGNED to execute arbitrary code │
│ (that's literally what notebooks do) │
│ │
│ 2. Run with elevated privileges │
│ (GPU access, cloud SDKs, network access) │
│ │
│ 3. Contain high-value credentials │
│ (AWS keys, API tokens, DB connections) │
│ │
│ 4. Often exposed to the network │
│ (for collaboration and remote access) │
│ │
│ 5. Security hardening is an afterthought │
│ (focus on features and UX, not security) │
│ │
│ 6. Users are researchers, not security experts │
│ (default configs, weak passwords, no VPN) │
└─────────────────────────────────────────────────┘
该 Python 脚本通过多步骤分析检测存在漏洞的 Marimo 实例。
工作原理:
/api/status、/api/health 和 /,在响应内容和响应头中查找 Marimo 标识/terminal/ws 发送安全的 HTTP Upgrade 请求(不通过该连接传输任何数据)/terminal/ws(应要求认证)与 /ws(已知要求认证)的行为,以确认不一致性不会在目标系统上执行任何命令。 WebSocket 握手仅进行测试,不会通过该连接发送任何数据。该检查完全被动,对生产环境安全。
用法:```bash
pip install -r requirements.txt
python CVE-2026-39987_Marimo_RCE_detector.py -t http://marimo-host:2718
python CVE-2026-39987_Marimo_RCE_detector.py -t https://marimo-host:443
python CVE-2026-39987_Marimo_RCE_detector.py -f targets.txt -o results.json -v
python CVE-2026-39987_Marimo_RCE_detector.py -t http://10.0.0.5:2718 --timeout 15
**选项:**
| 标志 | 描述 | 默认值 |
|------|-------------|---------|
| `-t`, `--target` | 单个目标 URL(例如 `http://host:2718`) | — |
| `-f`, `--file` | 包含目标 URL 的文件,每行一个(支持 `#` 注释) | — |
| `-o`, `--output` | 将结果保存到 JSON 文件 | — |
| `--timeout` | 连接超时时间(秒) | `10` |
| `--verify-ssl` | 启用 SSL 证书验证 | 已禁用 |
| `-v`, `--verbose` | 详细输出,包含完整信息 | 关闭 |
**示例输出:**```
[*] CVE-2026-39987 Marimo Pre-Auth RCE Scanner
[*] Scanning 1 target(s)...
[*] NOTE: This scanner only checks for endpoint exposure.
[*] No commands are executed on target systems.
======================================================================
Target: http://10.0.0.5:2718
Scan Time: 2026-04-14T16:00:00Z
Risk Level: CRITICAL
======================================================================
Is Marimo: YES
Marimo Version: 0.19.2
/terminal/ws Open: YES — UNAUTHENTICATED
Vulnerable: YES
*** CRITICAL: Pre-authenticated RCE is possible! ***
*** An attacker can get a full PTY shell without any auth ***
Details:
- Marimo instance detected via /api/status
- Marimo version: 0.19.2
- WebSocket upgrade accepted — /terminal/ws accessible WITHOUT auth
- CONFIRMED: /terminal/ws accepts unauthenticated connections while
/ws requires auth — classic CVE-2026-39987 signature
- Version 0.19.2 <= 0.20.4 — VULNERABLE to pre-auth RCE
======================================================================
[*] Scan Complete: 1 targets scanned
[*] Marimo Instances: 1 | Vulnerable: 1 | Critical: 1
======================================================================
sudo cp CVE-2026-39987_Marimo_RCE.nse /usr/share/nmap/scripts/ sudo nmap --script-updatedb
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE
nmap -p 2718,8080,8443,443 --script CVE-2026-39987_Marimo_RCE
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE 10.0.0.0/24
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE -iL targets.txt
nmap -sV -p 2718 --script CVE-2026-39987_Marimo_RCE
**Nmap 输出示例:**```
PORT STATE SERVICE
2718/tcp open http
| CVE-2026-39987_Marimo_RCE:
| VULNERABLE:
| Marimo Pre-Auth RCE (CVE-2026-39987)
| State: VULNERABLE
| Risk level: CRITICAL
| Marimo Version: 0.19.2
| /terminal/ws: accessible without authentication
| Description:
| The Marimo /terminal/ws WebSocket endpoint accepts connections
| without authentication, enabling pre-authenticated RCE.
| An attacker can obtain a full PTY shell without any credentials.
| References:
|_ https://nvd.nist.gov/vuln/detail/CVE-2026-39987
如果你可以访问 Marimo 实例:```bash
curl -s http://:2718/api/status | python3 -m json.tool
curl -s -o /dev/null -w "%{http_code}"
-H "Upgrade: websocket"
-H "Connection: Upgrade"
-H "Sec-WebSocket-Key: dGVzdC1rZXktMTIzNDU2Nzg="
-H "Sec-WebSocket-Version: 13"
http://:2718/terminal/ws
curl -s -o /dev/null -w "%{http_code}"
-H "Upgrade: websocket"
-H "Connection: Upgrade"
-H "Sec-WebSocket-Key: dGVzdC1rZXktMTIzNDU2Nzg="
-H "Sec-WebSocket-Version: 13"
http://:2718/ws
如果 `/terminal/ws` 返回 101 而 `/ws` 返回 401/403,这就是典型的 CVE-2026-39987 特征。
---
## 入侵指标
请在您的环境中留意以下迹象:
| 指标 | 检查位置 | 需要关注的内容 |
|:---|:---|:---|
| 未经授权的 WebSocket 连接 | 服务器/代理日志 | 来自意外 IP 到 `/terminal/ws` 的连接 |
| PTY 会话创建 | 进程监控 | Marimo 服务器生成的意外 shell 进程 |
| 文件访问 | 文件审计日志 | 读取 `.env`、凭据文件或 SSH 密钥 |
| 凭据使用 | 云提供商审计日志 | 使用存储在 Marimo 环境中的密钥进行的 API 调用 |
| 出站数据传输 | 网络监控 | 来自 Marimo 主机的异常出口流量 |
**调查命令:**```bash
# Check for active WebSocket connections
ss -tnp | grep <MARIMO_PORT>
# Review process tree for unexpected shells
ps aux --forest | grep -A5 marimo
# Check if .env or credential files were recently accessed
stat .env
stat ~/.aws/credentials
# Review cloud provider activity logs for unauthorized access
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=<KEY_ID>
# Check for unauthorized outbound connections
netstat -tnp | grep ESTABLISHED | grep -v 127.0.0.1
立即行动(现在就做):
pip install --upgrade marimo)短期措施(本周内):
.env 文件和环境变量,确认是否有敏感数据可能已暴露长期措施:
Kerem Oruç — 网络安全工程师