rootdirective-sec/CVE-2026-39987-Lab
GitHub: rootdirective-sec/CVE-2026-39987-Lab
基于 Docker 的 CVE-2026-39987 复现环境,用于本地安全研究 marimo 预认证 Terminal WebSocket RCE 漏洞的成因与修复差异。
Stars: 0 | Forks: 0
# CVE-2026-39987 — marimo 预认证 Terminal WebSocket RCE 实验环境
## 概述
CVE-2026-39987 是 **marimo**(一个响应式 Python notebook 框架)中的一个严重的预认证远程代码执行漏洞。
存在漏洞的行为位于 terminal WebSocket 端点:
```
/terminal/ws
```
在受影响的版本中,该端点无需有效身份验证即可访问,并能创建交互式终端会话。能够访问存在漏洞的 marimo 编辑服务器的未经身份验证的攻击者,可能会以 marimo 进程的权限执行命令。
本实验环境在受控的本地 Docker 环境中重现了该漏洞:
| 服务 | 版本 | URL | 预期行为 |
| --------- | ------------: | ----------------------- | -------------------------------------------------------------------------------- |
| `vuln` | marimo `0.20.4` | `http://127.0.0.1:8081` | `/terminal/ws` 接受未经身份验证的 WebSocket 连接 |
| `patched` | marimo `0.23.0` | `http://127.0.0.1:8082` | `/terminal/ws` 以 `403 Forbidden` 拒绝未经身份验证的 WebSocket 连接 |
本实验的目的不是提供武器化的漏洞利用。目的是使用仅限本地、可重现的证据,来展示受影响版本和已修补版本之间可观察到的安全差异。
## 漏洞概述
### 受影响组件
受影响的组件是 marimo 的 terminal WebSocket 端点:
```
/terminal/ws
```
该端点由 marimo 的编辑环境使用,通过浏览器连接的 WebSocket 会话提供终端功能。
### 根本原因
存在漏洞的端点在未强制执行受保护的 marimo 编辑功能所期望的相同身份验证检查的情况下,接受了 WebSocket 连接。
重要的区别在于:
```
vulnerable behavior:
unauthenticated client can connect to /terminal/ws
terminal session is created
commands can be sent through the WebSocket
patched behavior:
unauthenticated client is rejected
WebSocket handshake fails with 403 Forbidden
terminal session is not created
```
该补丁在允许建立终端会话之前,向 terminal WebSocket 流程添加了身份验证。
### 影响
如果存在漏洞的 marimo 编辑服务器暴露在可达的网络中,未经身份验证的攻击者可能会以运行 marimo 进程的用户身份执行命令。
在本实验环境中,marimo 进程被有意地以低权限非 root 用户身份运行:
```
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
```
这使得演示更安全,同时依然能够证明该漏洞的存在。
## 实验目标
本实验旨在证明三点:
1. 存在漏洞的版本接受到 `/terminal/ws` 的未经身份验证的 WebSocket 连接。
2. 可以通过该未经身份验证的 terminal WebSocket 执行良性命令。
3. 已修补的版本以 `403 Forbidden` 拒绝相同的未经身份验证的 WebSocket 尝试。
本实验有意避免了破坏性命令、持久化、反向 shell、凭据转储或任何面向互联网的目标。
## 仓库结构
```
.
├── docker-compose.yml
├── vuln/
│ ├── Dockerfile
│ └── notebook.py
├── patched/
│ ├── Dockerfile
│ └── notebook.py
├── poc/
│ ├── poc.py
│ ├── rce_poc.py
│ └── requirements.txt
├── SAFETY.md
├── README.md
└── .gitignore
```
### 重要文件
| 文件 | 用途 |
| ---------------------- | ----------------------------------------------------------------------------- |
| `docker-compose.yml` | 定义存在漏洞的和已修补的 marimo 服务 |
| `vuln/Dockerfile` | 构建存在漏洞的 marimo 服务 |
| `patched/Dockerfile` | 构建已修补的 marimo 服务 |
| `vuln/notebook.py` | 存在漏洞的服务使用的最小化 marimo notebook 文件 |
| `patched/notebook.py` | 已修补的服务使用的最小化 marimo notebook 文件 |
| `poc/poc.py` | 仅运行良性命令的最小危害证明脚本 |
| `poc/rce_poc.py` | 用于在本地实验环境中观察终端行为的实时学习客户端 |
| `poc/requirements.txt` | PoC 脚本的 Python 依赖项 |
| `SAFETY.md` | 安全规则和范围边界 |
## 实验架构
```
Host machine
127.0.0.1:8081 ─────► vuln container
marimo 0.20.4
/terminal/ws accepts unauthenticated WebSocket
127.0.0.1:8082 ─────► patched container
marimo 0.23.0
/terminal/ws rejects unauthenticated WebSocket
```
这两个服务都公开了 marimo 的内部端口 `2718`,但主机端口不同:
```
vuln -> 127.0.0.1:8081
patched -> 127.0.0.1:8082
```
这些服务仅绑定到 `127.0.0.1`。它们不打算暴露给局域网或互联网。
## Docker 加固说明
容器已在可能的情况下配置了多项防护措施:
```
- bind ports to 127.0.0.1 only
- run as a non-root user
- drop Linux capabilities
- enable no-new-privileges
- use a read-only root filesystem
- provide only limited tmpfs write locations
- isolate services inside a dedicated Docker bridge network
```
这些控制措施并没有消除存在漏洞的服务中的漏洞。它们只是减小了本地演示的波及范围。
## 前置条件
已测试的假设环境:
```
- Linux x86_64 or macOS with Docker Desktop
- Docker Compose v2
- Python 3.9+
- Localhost-only testing
```
所需工具:
```
docker --version
docker compose version
python3 --version
```
## 设置
克隆或进入项目目录:
```
cd cve-2026-39987
```
构建并启动两个服务:
```
docker compose up -d --build
```
检查两个容器是否都在运行:
```
docker compose ps
```
预期的服务:
```
cve-2026-39987-vuln
cve-2026-39987-patched
```
## 验证版本
检查存在漏洞的服务版本:
```
docker compose exec vuln marimo --version
```
预期结果:
```
0.20.4
```
检查已修补的服务版本:
```
docker compose exec patched marimo --version
```
预期结果:
```
0.23.0
```
## 安装 PoC 依赖
创建一个 Python 虚拟环境:
```
python3 -m venv .venv
source .venv/bin/activate
```
安装依赖:
```
python -m pip install -r poc/requirements.txt
```
## 最小危害 PoC
主要的证明脚本是:
```
poc/poc.py
```
它尝试连接到:
```
/terminal/ws
```
然后它仅发送良性的证明命令:
```
id
whoami
hostname
```
不使用反向 shell、文件写入、持久化、凭据访问或破坏性命令。
### 测试存在漏洞的服务
运行:
```
python poc/poc.py --base-url http://127.0.0.1:8081
```
预期结果:
```
[*] Target WebSocket: ws://127.0.0.1:8081/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[+] websocket connected without credentials
[result] VULNERABLE: unauthenticated command execution observed
[proof]
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
```
如果脚本打印了原始终端输出但包含以下代码块,则漏洞依然被证实:
```
CVE39987_PROOF_START
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
CVE39987_PROOF_END
```
这意味着 WebSocket 连接成功,并且命令输出已从终端会话返回。
### 测试已修补的服务
运行:
```
python poc/poc.py --base-url http://127.0.0.1:8082
```
预期结果:
```
[*] Target WebSocket: ws://127.0.0.1:8082/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[-] websocket connection rejected/failed: Handshake status 403 Forbidden
[result] not exploitable by this unauthenticated check
```
这表明已修补的服务在创建终端会话之前拒绝了未经身份验证的访问。
## 实时学习客户端
以下文件仅用于本地学习:
```
poc/rce_poc.py
```
它通过连接到存在漏洞的 terminal WebSocket 并允许终端交互,以一种更实时的方式演示了相同的问题。
仅针对本地 Docker 实验环境使用此客户端。
### 针对存在漏洞的服务运行
```
python poc/rce_poc.py --base-url http://127.0.0.1:8081
```
预期行为:
```
[+] Connected successfully (Pre-Auth RCE)
✓ Interactive shell is ready to use!
```
可尝试的安全命令:
```
id
whoami
hostname
pwd
python -c 'import marimo; print(marimo.__version__)'
exit
```
### 针对已修补的服务运行
```
python poc/rce_poc.py --base-url http://127.0.0.1:8082
```
预期行为:
```
[-] Connection Failed: 403 Forbidden
[!] This is likely the PATCHED version.
The WebSocket endpoint is now protected.
```
### 实时模式的安全说明
实时客户端的存在是为了帮助理解存在漏洞的 WebSocket 的行为方式。不应将其用于此本地实验环境之外的任何系统。
对于作品集展示,推荐的实质性证据仍然是 `poc/poc.py`,因为它是受限的、可重复的且最小危害的。
## 预期的安全差异
主要的实验结果应概括为:
```
vuln / marimo 0.20.4:
unauthenticated WebSocket handshake to /terminal/ws succeeds
benign command output is observable
patched / marimo 0.23.0:
unauthenticated WebSocket handshake to /terminal/ws fails
server returns 403 Forbidden
no terminal session is created
```
这是 CVE 复现的核心证据。
## 手动 WebSocket 检查
如果您想手动检查该行为,请使用支持 WebSocket 的客户端连接到:
```
ws://127.0.0.1:8081/terminal/ws
ws://127.0.0.1:8082/terminal/ws
```
预期结果:
```
8081 -> connection accepted
8082 -> 403 Forbidden
```
首选 PoC 脚本,因为它们能产生更清晰的证据。
## 检测与监控思路
在真实环境中,有用的指标包括:
```
- WebSocket requests to /terminal/ws
- unauthenticated access attempts to marimo edit servers
- unexpected terminal sessions spawned by marimo
- commands launched by the marimo process
- access to .env, SSH keys, cloud credentials, or notebook secrets
- outbound network traffic shortly after /terminal/ws connections
```
可供检查的示例本地痕迹:
```
docker compose logs vuln
docker compose logs patched
docker compose ps
docker compose exec vuln ps aux
```
## 清理
停止并移除容器:
```
docker compose down
```
同时移除卷:
```
docker compose down -v
```
根据需要移除本地 Python 虚拟环境:
```
rm -rf .venv
```
## 安全规则
本仓库仅用于本地安全研究和作品集展示。
允许:
```
- localhost testing
- Docker-only reproduction
- benign proof commands such as id, whoami, hostname
- comparing vulnerable and patched behavior
- documenting root cause and detection ideas
```
不允许:
```
- testing against public marimo servers
- testing systems you do not own or administer
- reverse shells
- persistence
- credential theft
- destructive commands
- lateral movement
- botnet or malware behavior
```
## 免责声明
本项目仅用于授权的本地测试和防御性安全教育。请勿将这些脚本用于您不拥有或未获得明确测试许可的系统。
## 参考
* GitHub 安全通告 — GHSA-2679-6mx9-h9xc:
https://github.com/advisories/GHSA-2679-6mx9-h9xc
* NVD — CVE-2026-39987:
https://nvd.nist.gov/vuln/detail/CVE-2026-39987
* marimo 上游仓库:
https://github.com/marimo-team/marimo
* 修补提交 — 为 terminal WebSocket 添加身份验证:
https://github.com/marimo-team/marimo/commit/c24d4806398f30be6b12acd6c60d1d7c68cfd12a
* 修补 PR — marimo PR #9098:
https://github.com/marimo-team/marimo/pull/9098
标签:0day漏洞, CISA项目, CVE-2026-39987, Docker漏洞复现, marimo, Python, RCE, WebSocket漏洞, Web安全, 交互式终端, 开源框架漏洞, 无后门, 未授权访问, 漏洞靶场, 版权保护, 终端执行, 编程工具, 网络安全, 蓝队分析, 请求拦截, 越权访问, 远程代码执行, 逆向工具, 防御检测, 隐私保护, 预认证漏洞