仅检查登录状态的管理员终端引导路由,使得普通团队成员能够驱动 Coolify 的实时终端后端并在团队服务器上执行命令。
我在审查 Coolify(一个开源的自托管 PaaS)时发现了这个问题,当时我心中有一个非常直接的安全性问题:
终端访问是否在后端信任边界处实际强制执行,还是仅在 UI 中?
在这个案例中,答案是糟糕的。
Coolify 本意是将终端访问限制为团队管理员和所有者,但实时终端引导路由仅检查用户是否已登录。这使得低权限的团队成员能够满足 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
CVE: CVE-2026-34048
这影响了 Coolify,一个开源的自托管 PaaS。在其官方网站上,Coolify 声称拥有 3,641+ 家云客户,并自称是一个用于部署网站、数据库、Web 应用程序以及 280+ 种一键服务 的平台。其官方 v4.0 更新日志还提到,数千家公司和人员已经在生产环境中使用 Coolify 1-2 年。
photo0
低权限团队成员会话 -> /terminal/auth 和 /terminal/auth/ips 仅检查登录状态 -> 实时 websocket 信任这些响应 -> 成员枚举团队服务器和可见的 SSH 密钥 UUID -> /terminal/ws 接受会话 -> 生成 SSH 支持的 PTY -> 在团队服务器上获得 shell 访问
Coolify 是一个自托管的 PaaS 和部署平台。
它管理:
最后这一项功能在这里是关键。
一旦一个平台能够打开托管主机的终端,其授权模型就不再只是应用逻辑了。 它变成了一个基础设施信任边界。
重要的问题不在于 /terminal 页面看起来是否只对管理员可见。
真正的问题是:
后端终端路径在创建 websocket 会话时是否实际强制执行相同的授权边界?
在这个案例中,答案是没有。
终端功能是基础设施软件中价值最高的攻击面之一。
为什么呢?
因为任何以下方面的不匹配:
都可能将一个普通应用用户变成一个能获取 shell 的操作者。
这正是为什么这个攻击面值得测试。
我并不是在寻找随机的崩溃或表面的权限错误。
我寻找的是更强类型的失败:
一个仅限管理员的功能是否依赖于比 UI 所暗示的更弱的后端信任检查?
这就是正确的问题。
我并没有通过模糊测试随机端点并希望发现有趣的东西来接近 Coolify。
更强的方法是先识别最高风险的边界。
对于 Coolify 来说,这个边界就是终端工作流:
这使得引导路由成为正确的检查点。
而问题就在这里。
根本原因是终端 UI 和终端 websocket 引导路由之间的授权不匹配。
在有漏洞的版本中:
GET /terminal 由 can.access.terminal 保护POST /terminal/auth 只检查 auth()->check()POST /terminal/auth/ips 只检查 auth()->check()这意味着 UI 受到终端授权的门控,但后端信任边界仅由简单的已验证会话存在性来门控。
然后实时服务完全信任这两个路由。
在 docker/coolify-realtime/terminal-server.js 中:
verifyClient() 向 /terminal/auth 发送 POST 请求/terminal/auth/ips 发送 POST 请求这就是整个漏洞链。
因为一个普通团队成员可以从常规的应用表面构建所需的输入:
/servers 暴露了可见的服务器 UUID/server/{uuid} 以渲染的表单字段形式暴露了 ip、user 和 port/security/private-key 暴露了可见的团队私钥 UUID/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
因此利用路径非常简单:
/terminal/auth/terminal/auth/ips/terminal/ws这不是理论上的不匹配。 这是实际的后端授权失败。
重要的区别在于后端信任和命令执行。
许多漏洞看起来像:
这本身是不够的。
真正的问题是:
低权限用户是否仍然能满足关键的后端信任检查?
在这个案例中,答案是肯定的。
这不是:
这是:
这就是为什么这是一个真正的安全问题。
我在一个受控的本地实验室中验证了这一点,该实验室基于以下版本构建:
06f60c9a98bead0c932c6adf7fd43a45d9149048
实验室使用:
http://127.0.0.1:18000[email protected]localhost -> coolify-testing-host:22 as rootsshws://127.0.0.1:6002/terminal/ws该成员账户本不应通过普通管理员界面拥有终端访问权限。
这确立了预期的安全边界。
使用成员会话,我发送了:
POST /terminal/authPOST /terminal/auth/ips两者均成功。
/terminal/auth/ips 返回了终端授权的主机,包括:
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
这证明了后端引导路由信任了成员会话。
从普通的认证页面,同一个成员可以枚举:
这足以驱动终端路径,而无需泄露密钥材料。
使用相同的已验证会话和 XSRF 令牌,我连接到了:
ws://127.0.0.1:6002/terminal/ws
负载使用了终端后端所期望的相同命令格式:
{"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"]}
websocket 返回了:
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
这是重要的证明。
不仅仅是:
而是通过仅限管理员使用的终端路径在托管主机上实际执行命令。
这个链条中的任何一部分本身就已经值得关注。
例如:
/terminal/auth 的访问/terminal/auth/ips 的访问但这仍然可能被驳回。
更强的验证是端到端的:
这填补了“理论上的授权漏洞”与“实际的基础设施影响”之间的差距。
它也使得严重性的辩护更加容易。
该问题被正确分类为 严重。
分类是:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
这是合理的。
这里的主张并不是说一个未认证的攻击者可以从零开始获得 shell 访问。
主张是:
这是一个从应用程序 RBAC 失败到托管主机影响的重大范围变化。
因此,即使权限是低的而不是没有,结果仍然是明显的严重。
有些人低估了以 PR:L 开头的漏洞。
当涉及的特性是终端访问时,那是一个错误。
真正的问题不是:
“攻击者是否已经登录了?”
真正的问题是:
“当后端授权错误时,那个低权限用户能够触及什么?”
在这个案例中,答案是:
这远远超出了普通的成员权限漏洞。
最低限度的正确修复是直接的:
can.access.terminal 应用到 POST /terminal/auth 和 POST /terminal/auth/ips这解决了直接的信任边界失败。
在我的本地验证补丁中,将终端授权中间件应用于这两个路由移除了成员到终端的路径。
但更强的教训是,后端不应信任攻击者控制的 SSH 命令字符串作为目标元数据的主要来源。
推荐的加固措施是:
这就是你希望为终端功能所做的修复:
该问题通过 GitHub 的安全报告流程私下报告。
报告包括:
该问题后来被分配:
CVE-2026-34048
这里的主要教训很简单:
如果后端引导通道信任更弱的状态,仅限管理员的 UI 就无关紧要。
这是真正的问题类别。
如果以下情况成立,这些都不重要:
一旦平台管理基础设施,授权不匹配就不再是普通的访问控制错误。 它们变成了影响基础设施的漏洞。
这是真正的收获。
这个漏洞并非关于巧妙的负载。
而是关于识别正确的信任边界。
Coolify 本意是仅限管理员访问终端。 但实时终端后端信任了仅检查用户是否登录的路由。
从这里,一个低权限的团队成员能够驱动 websocket 终端路径,并在团队服务器上达到 shell 执行。
这就是为什么它变成了 CVE-2026-34048。