0xmrma/CVE-2026-34048
GitHub: 0xmrma/CVE-2026-34048
该项目详细记录了 Coolify 平台终端功能中因后端引导路由缺少管理员权限校验而导致普通成员可在团队服务器上远程执行命令的越权漏洞(CVE-2026-34048)。
Stars: 0 | Forks: 0
# CVE-2026-34048
仅限管理员的终端引导路由仅检查了登录状态,这使得普通团队成员能够驱动 Coolify 的 realtime 终端后端,并在团队服务器上执行命令。
## 简介
我在审查 **Coolify**(一个开源的自托管 PaaS)时发现了这个问题,当时脑海中有一个非常直接的安全问题:
**终端访问是否真正在后端信任边界得到了强制执行,还是仅仅在 UI 层面?**
在这种情况下,答案很糟糕。
Coolify 本意是将终端访问限制为团队管理员和所有者,但 realtime 终端引导路由仅检查了用户是否已登录。这让低权限团队成员能够通过 websocket 终端信任检查,并在团队服务器上执行命令。
我在基于易受攻击版本构建的本地实验室中进行了端到端验证,随后进行了私下报告。该问题被分配了 **CVE-2026-34048**:
```
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
```
**Coolify:** [GitHub 上的 Coolify](https://github.com/coollabsio/coolify)
**CVE:** CVE-2026-34048
这影响了 **Coolify**,一个开源的自托管 PaaS。在其官方网站上,Coolify 声明拥有 **3,641+ 云客户**,并将自身定位为部署网站、数据库、Web 应用程序和 **280+ 一键部署服务**的平台。其官方 v4.0 更新日志还声明,**数千家企业和个人已在生产环境中使用 Coolify 1-2 年**。
## 攻击链
`低权限团队成员会话 -> /terminal/auth 和 /terminal/auth/ips 仅检查登录状态 -> realtime websocket 信任这些响应 -> 成员枚举团队服务器和可见的 SSH key UUID -> /terminal/ws 接受该会话 -> 生成基于 SSH 的 PTY -> 获得团队服务器上的 shell 访问权限`
## Coolify 是做什么的
**Coolify** 是一个自托管的 PaaS 和部署平台。
它管理:
- 服务器
- 应用程序
- 部署
- 私钥
- 团队权限
- 对受管基础设施的终端访问
这最后一项能力是这里的重点。
一旦一个平台可以打开通往受管主机的终端,其授权模型就不再仅仅是应用层逻辑了。
它变成了一个基础设施信任边界。
重要的问题不在于 `/terminal` 页面看起来是否仅限管理员。
真正的问题是:
**后端终端路径在创建 websocket 会话时,是否真正强制执行了相同的授权边界?**
在这种情况下,它没有。
## 为什么这个漏洞值得研究
终端功能是基础设施软件中价值最高的攻击面之一。
为什么?
因为以下各项之间的任何不匹配:
- UI 授权
- 后端授权
- websocket 引导逻辑
- 主机命令执行
都能将一个普通的应用程序用户变成一个具备 shell 操作能力的攻击者。
这正是这个攻击面值得测试的原因。
我不是在寻找随机的崩溃或表面的权限漏洞。
我在寻找一类更严重的故障:
这就是正确的问题。
## 我关注的边界
我没有通过模糊测试随机 endpoint 来接触 Coolify,并期望发现一些有趣的东西。
更强的方法是首先识别出最高风险的边界。
对于 Coolify 来说,那个边界就是终端工作流:
- UI 声称终端访问是受限的
- 终端服务是基于 websocket 的
- websocket 服务通常有独立的引导信任逻辑
- 终端命令最终从应用程序状态跨越到主机执行
这使得引导路由成为正确的调查方向。
而问题正是出在这里。
## 根本原因
根本原因是终端 UI 和终端 websocket 引导路由之间的授权不匹配。
在存在漏洞的版本中:
- `GET /terminal` 受 `can.access.terminal` 保护
- `POST /terminal/auth` 仅检查 `auth()->check()`
- `POST /terminal/auth/ips` 仅检查 `auth()->check()`
这意味着 UI 受到了终端授权的控制,但后端信任边界仅由简单的已认证会话存在性来控制。
随后,realtime 服务完全信任了这两个路由。
在 `docker/coolify-realtime/terminal-server.js` 中:
- `verifyClient()` 向 `/terminal/auth` 发送了 POST 请求
- websocket 会话建立向 `/terminal/auth/ips` 发送了 POST 请求
- websocket 处理程序在仅检查目标主机是否出现在返回的主机列表中之后,就接受了攻击者提供的终端命令输入
这就是整个漏洞链。
### 为什么这是可利用的
因为普通团队成员可以从常规应用程序界面获取所需的输入:
- `/servers` 暴露了可见的服务器 UUID
- `/server/{uuid}` 在渲染的表单字段中暴露了 `ip`、`user` 和 `port`
- `/security/private-key` 暴露了可见的团队私钥 UUID
- 终端路径通过以下形式的确定性路径引用密钥:
```
/var/www/html/storage/app/ssh/keys/ssh_key@
```
所以利用路径非常直接:
- 以非管理员团队成员身份登录
- 调用 `/terminal/auth`
- 调用 `/terminal/auth/ips`
- 枚举可见的服务器
- 枚举可见的密钥 UUID
- 连接到 `/terminal/ws`
- 发送后端期望的相同 SSH 命令格式
- 接收来自团队主机的 shell 输出
这不是理论上的不匹配。
这是一个实际的后端授权失效。
## 为什么这是一个安全问题,而不仅仅是 UI 不匹配
重要的区别在于后端信任和命令执行。
很多漏洞看起来像:
- “按钮被隐藏了”
- “页面被阻止了”
- “UI 提示你不应该在这里”
光有这些是不够的。
真正的问题是:
在这里,答案是肯定的。
这不是:
- 一个损坏的菜单
- 一个缺失的前端检查
- 一个表面的路由问题
而是:
- websocket 引导授权过于薄弱
- 终端主机授权源自那个薄弱的信任边界
- 在受管基础设施上实现了真实的 shell 访问
这就是为什么这是一个真正的安全问题。
## PoC
我在基于以下内容构建的受控本地实验室中对此进行了验证:
```
06f60c9a98bead0c932c6adf7fd43a45d9149048
```
该实验室使用了:
- base URL: `http://127.0.0.1:18000`
- 低权限成员账户: `test2@example.com`
- 目标服务器: `localhost -> coolify-testing-host:22 as root`
- 可见密钥 UUID: `ssh`
- websocket endpoint: `ws://127.0.0.1:6002/terminal/ws`
### 步骤 1:确认 UI 边界
原本的设计意图是,成员账户不通过常规的管理员面向 UI 获得终端访问权限。
这确立了预期的安全边界。
### 步骤 2:直接调用引导路由
使用该成员会话,我发送了:
- `POST /terminal/auth`
- `POST /terminal/auth/ips`
两者都成功了。
`/terminal/auth/ips` 返回了包含以下内容的终端授权主机:
```
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
```
这证明后端引导路由信任了该成员会话。
### 步骤 3:枚举服务器和密钥元数据
从常规的认证页面中,同一成员可以枚举出:
- 可见的服务器 UUID
- 服务器连接字段
- 可见的团队私钥 UUID
这足以驱动终端路径,而无需泄露秘密密钥材料。
### 步骤 4:打开终端 websocket
使用相同的已认证会话和 XSRF token,我连接到了:
```
ws://127.0.0.1:6002/terminal/ws
```
### 步骤 5:发送终端命令 payload
该 payload 使用了终端后端期望的相同命令格式:
```
{"command":["timeout 30 ssh -i /var/www/html/storage/app/ssh/keys/ssh_key@ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o RequestTTY=no -o LogLevel=ERROR -p '22' 'root'@'coolify-testing-host' 'bash -se' << \\P0C\nprintf '__COOLIFY_POC_BEGIN__\\n'; id; whoami; hostname; printf '__COOLIFY_POC_END__\\n'\nP0C"]}
```
### 步骤 6:观察远程 shell 输出
websocket 返回了:
```
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
```
这是重要的证明。
不仅仅是:
- 路由访问
- 不仅仅是 websocket 接受
- 不仅仅是元数据暴露
而是通过仅限管理员的终端路径在受管主机上执行了实际命令。
## 为什么这个 PoC 很有说服力
这个链中的任何一部分本身就已经很有趣了。
例如:
- 成员有权访问 `/terminal/auth`
- 或成员有权访问 `/terminal/auth/ips`
但这仍然会留下被辩解的空间。
更强的验证是端到端的:
- 成员会话
- 后端引导成功
- websocket 接受
- PTY 创建
- 远程 shell 输出
这弥合了“理论上的授权漏洞”和“现实中实际的基础设施影响”之间的差距。
这也使得严重性更容易得到辩护。
## 严重程度和分类
此问题被正确分类为 **Critical**。
分类如下:
- **CWE-862**: 缺失授权
- **CVSS:**
```
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
```
这是合理的。
这不是声称未经身份验证的攻击者可以从无到有获得 shell 访问权限。
而是声称:
- 一个低权限的团队成员
- 可以通过后端终端信任检查
- 并在团队基础设施上达到执行命令的目的
这是从应用层 RBAC 故障到受管主机影响的一个重大范围转变。
因此,尽管权限是低而不是无,结果依然明显是严重的。
## 为什么这仍然值得报告
有些人低估了以 `PR:L` 开头的漏洞。
当受影响的功能是终端访问时,这是一种错误。
真正的问题不是:
真正的问题是:
在这种情况下,答案是:
- 主机选择数据
- 终端引导信任
- 基于 SSH 的 PTY 执行
- 团队服务器上的 shell 访问
这远远超出了普通的成员权限漏洞。
## 修复分析
最起码的正确修复方法很简单:
- 将 `can.access.terminal` 应用于 `POST /terminal/auth` 和 `POST /terminal/auth/ips` 两者
- 确保非管理员成员在两个路由上都被拒绝
- 添加回归测试覆盖:
- 拒绝未经身份验证的用户
- 拒绝已身份验证的成员
- 允许授权的管理员和所有者
这解决了直接的信任边界失效问题。
在我本地的验证补丁中,将终端授权中间件应用于这两个路由移除了成员到终端的路径。
但更深刻的教训是,后端不应该将攻击者控制的 SSH 命令字符串作为目标元数据的主要来源。
建议的强化措施是:
- 将终端请求绑定到在服务端授权的服务器或容器标识符
- 在执行命令时重新验证授权,而不仅仅是在打开 websocket 时
- 减少在安全决策中对客户端提供的终端命令结构的依赖
这就是你希望终端功能具备的那种修复措施:
- 修复直接的缺失授权问题
- 然后收紧更深层的信任模型
## 披露
此问题通过 GitHub 的安全报告流程进行了私下报告。
报告包括:
- 授权不匹配
- 受影响的路由
- realtime 后端信任路径
- 一个可工作的本地实验室验证
- 显示远程 shell 输出的端到端证明
该问题随后被分配了:
**CVE-2026-34048**
## 这个漏洞真正教会了我们什么
这里的主要教训很简单:
这才是真正的问题类别。
- 一个页面可以被正确保护。
- 一个菜单可以被正确隐藏。
- 一个终端界面可以被正确拦截。
如果满足以下条件,这一切都无济于事:
- websocket 引导路径仅检查登录状态
- 终端后端信任这些引导响应
- 并且生成的会话可以到达主机命令执行
一旦平台管理基础设施,授权不匹配就不再是普通的访问控制错误。
它们变成了影响基础设施的漏洞。
这才是真正的要点。
## 关键点
- websocket 引导 endpoint 是真正的安全边界
- 对于终端功能,仅有 UI 授权是不够的
- 当后端信任错误时,低权限用户仍然可以造成严重影响
- 对服务器元数据和可见密钥 UUID 的枚举使得此漏洞具有实际可操作性
- 在论证严重程度时,端到端的 runtime 验证至关重要
- 正确的修复是一致的后端授权,而不是更强的前端门控
## 结语
这个漏洞不是关于一个巧妙的 payload。
它是关于识别正确的信任边界。
Coolify 本意是让终端访问仅限管理员。
但 realtime 终端后端信任了那些仅检查用户是否登录的路由。
由此,低权限的团队成员可以驱动 websocket 终端路径,并在团队服务器上达到 shell 执行。
这就是它成为 **CVE-2026-34048** 的原因。
标签:PaaS, Web安全, 内存分配, 协议分析, 安全漏洞, 数据可视化, 权限提升, 自托管平台, 蓝队分析, 访问控制缺陷, 请求拦截, 运维部署