0xmrma/CVE-2026-46558

GitHub: 0xmrma/CVE-2026-46558

记录了 Plane 项目管理平台 V2 资产子系统中因缺少跨工作空间成员资格检查而导致的授权绕过漏洞(CVE-2026-46558)的详细分析与 PoC。

Stars: 0 | Forks: 0

# CVE-2026-46558 Plane 的 V2 资产子系统信任 workspace slug 和资产 UUID,而没有强制执行正确的成员资格检查,这使得一个经过身份验证的用户可以读取、复制、删除和覆盖其他 workspace 中的资产。 ## 简介 我在审查开源项目管理平台 **Plane** 时发现了这个问题,当时我脑海中有一个非常具体的问题: **V2 资产 endpoint 是否真的强制执行了 workspace 边界,还是它们过于信任攻击者提供的 workspace slug 和资产 ID?** 在这个案例中,答案是否定的。 Plane 的 V2 资产子系统暴露了两个相关的授权缺陷,破坏了任何经过身份验证的用户的 workspace 隔离: - workspace 级别的资产 endpoint 在执行资产操作之前没有强制执行目标 workspace 的成员资格检查 - 复制资产流程仅对目标 workspace 进行了授权,并信任源资产 UUID,而没有检查源 workspace 的访问权限 这使得跨 workspace 滥用资产成为可能。 在我验证的 PoC 中,workspace Bravo 中的一个普通用户能够: - 下载 Alpha 的私有上传资产 - 将该资产复制到 Bravo 自己的 workspace 中 - 删除 Alpha 的原始资产 - 用攻击者控制的内容覆盖 Alpha 的 workspace logo 该问题随后被分配了 **CVE-2026-46558**。 **Plane:** [GitHub 上的 Plane](https://github.com/makeplane/plane) **CVE:** CVE-2026-46558 这影响了 **Plane**,其官方网站显示它被**全球超过 50,000 个团队**使用。Plane 还强调了其强大的开源采用率,包括**超过 46,000 个 GitHub star** 和**超过 1,000,000 次 Docker pull**,并展示了如 **Tencent**、**Accenture**、**Microsoft** 和 **Amazon** 等组织。 photo0 ## 攻击链 `workspace B 中经过身份验证的攻击者 → workspace 级别的 V2 资产路由信任目标 workspace slug 和资产 UUID,而没有进行适当的成员资格检查 → 对 workspace A 的资产执行预签名读取/修补/删除 + 复制资产源查找信任已上传的源 UUID → 跨 workspace 泄露、复制、删除和品牌覆盖` ## Plane 是做什么的 **Plane** 是一个开源项目管理平台,用于管理: - 任务 - 事务 - 冲刺 - 文档 - 甄别 - workspace 级别的品牌标识和资产 这意味着它的资产子系统处于一个真实的信任边界上。 这里的重要问题不是 Plane 是否支持上传。 真正的问题是: **当一个经过身份验证的用户引用另一个 workspace 拥有的资产时,Plane 是否强制执行了 workspace 隔离?** 在这个案例中,它没有。 ## 为什么这个漏洞值得关注 许多多租户应用程序审查首先关注的是明显的管理员 endpoint 或直接的设置更新。 这忽略了一类非常常见且真实的漏洞: **通过共享文件或资产子系统进行的次级对象访问** 资产系统很容易出错,因为它们通常结合了: - 用户控制的标识符 - 存储层间接寻址 - 元数据驱动的对象链接 - 预签名 URL 生成 - 共享路由背后的多种实体类型 这正是租户边界悄无声息地减弱的地方。 这个问题与存储损坏无关。 它与 S3 本身无关。 它与上传的 MIME 处理无关。 这是一个**授权边界失效**: - 攻击者控制的标识符跨越了边界 - 服务器解析了跨 workspace 的对象 - 授权不完整或缺失 - 特权资产操作仍然成功 这足以造成一个真实的漏洞。 ## 我关注的边界 我没有通过盲目模糊测试随机 endpoint 或在没有模型的情况下猜测 UUID 来接触 Plane。 更强的方法是首先确定最有希望的隔离边界。 对于 Plane 来说,那就是 **V2 资产子系统**。 为什么? 因为当出现以下情况时,共享资产系统会变得危险: - 存在多个 workspace - 上传的对象由 UUID 引用 - workspace slug 是攻击者控制的路由输入 - 应用程序随后将成功的查找转换为预签名的下载或修改路径 这正是需要检查的边界。 而这正是漏洞所在之处。 ## 根本原因 这实际上是同一个子系统中的两个相关授权失败。 ### 根本原因 1:workspace 资产路由缺少成员资格强制执行 workspace 级别的资产路由通过以下方式暴露: - `apps/api/plane/app/urls/asset.py:50-56` 存在漏洞的处理器位于: - `apps/api/plane/app/views/asset/v2.py:314` - `apps/api/plane/app/views/asset/v2.py:379` - `apps/api/plane/app/views/asset/v2.py:400` - `apps/api/plane/app/views/asset/v2.py:409` 问题很简单。 `WorkspaceFileAssetEndpoint` 接受一个 workspace slug 和资产 UUID,然后直接解析如下对象: ``` workspace = Workspace.objects.get(slug=slug) ``` 和: ``` asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug) ``` 而没有首先强制要求调用者实际上是该目标 workspace 的授权成员。 这意味着该 endpoint 仍然可以针对另一个 workspace 的对象: - 创建资产 - 完成资产 - 删除资产 - 返回预签名的下载 URL ### 根本原因 2:复制资产信任源资产 UUID 复制资产路由通过以下方式映射: - `apps/api/plane/app/urls/asset.py:100-101` 存在漏洞的逻辑位于: - `apps/api/plane/app/views/asset/v2.py:736-780` 目标 workspace 有一个授权装饰器。 但源资产查找没有。 源对象是通过以下方式加载的: ``` original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first() ``` 这意味着调用者只需要: - 对目标 workspace 的有效访问权限 - 一个已上传的源资产 UUID 并没有检查调用者是否属于实际拥有该资产的源 workspace。 这就是整个第二个漏洞。 ## 为什么这是一个安全问题,而不仅仅是糟糕的访问逻辑 重要的区别在于跨 workspace 的影响。 许多授权漏洞被轻描淡写为: 这没有抓住重点。 真正的问题不是: 真正的问题是: 在 Plane 中,这个答案是否定的。 这将可能看起来像普通对象处理的东西变成了一个真实的多租户安全问题。 这两者之间有明显的区别: - 在你自己的 workspace 内进行的经过身份验证的访问 - 以及跨越另一个租户边界的经过身份验证的访问 这个问题 firmly 属于第二种情况。 ## PoC 我在本地针对 **Plane Community Edition 1.2.3** 验证了该问题,在两个不相关的 workspace 中使用了两个普通用户: - workspace `alpha-20260323072017` 中的 Alpha - workspace `bravo-20260323072017` 中的 Bravo 我使用 Alpha 在一个项目事务中创建了一个合法的私有上传资产。 我在本次运行中验证的私有资产 ID 是: ``` 6ed6ed62-d1b2-4399-8220-336c01b7d72c ``` ### 案例 1:从另一个 workspace 未经授权的读取 作为 Bravo,我请求了: ``` GET /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/ ``` Plane 返回了: ``` HTTP/1.1 302 Found ``` 其中包含了 Alpha 资产的预签名下载 URL。 下载的文件哈希与 Alpha 的原始私有资产完全匹配: ``` original: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e unauthorized read: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e ``` 这证明了读取路径成功地跨越了 workspace 边界。 ### 案例 2:通过源 UUID 信任进行跨 workspace 复制 作为 Bravo,我随后请求了: ``` POST /api/assets/v2/workspaces/bravo-20260323072017/duplicate-assets/6ed6ed62-d1b2-4399-8220-336c01b7d72c/ ``` Plane 返回了: ``` HTTP/1.1 200 OK ``` 并创建了一个攻击者端的复制资产: ``` 72d51497-ccc1-4546-ba14-28fae5d37dbb ``` 复制文件的 SHA-256 与 Alpha 的原始资产完全匹配: ``` 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e ``` 这证明了仅凭源资产 UUID 就足以将跨 workspace 内容复制到攻击者控制的 workspace 中。 ### 案例 3:未经授权删除受害者资产 作为 Bravo,我随后发送了: ``` DELETE /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/ ``` Plane 返回了: ``` HTTP/1.1 204 No Content ``` 当 Alpha 随后获取该资产时,服务器返回了: ``` HTTP/1.1 404 Not Found ``` 这证明了跨 workspace 的完整性影响,而不仅仅是泄露。 ### 案例 4:未经授权的 workspace logo 覆盖 作为 Bravo,我通过存在漏洞的 workspace 级别资产路由针对 Alpha 的 workspace 创建了一个 `WORKSPACE_LOGO` 资产,上传了攻击者控制的内容,并完成了它。 之后,Alpha 的 workspace 元数据指向了攻击者控制的 logo 资产: ``` c1032f06-3cf5-4f7e-b139-e6976d8c567d ``` 下载的最终 logo 哈希与攻击者的 payload 完全匹配: ``` expected: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b observed: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b ``` 这证明了一个可见的跨 workspace 覆盖路径,而不仅仅是隐藏的后端访问问题。 ## 为什么完整链条很重要 上述任何一个结果都已经足以证明这是一个真实的漏洞报告。 但是,验证完整的链条之所以重要,有两个原因。 ### 第一 它表明这个问题不仅限于只读暴露。 同一个脆弱的边界实现了: - 泄露 - 复制 - 删除 - 覆盖 这使得其影响比狭义的“可以获取一个文件”的 IDOR 要严重得多。 ### 第二 它表明这两个代码路径是相关的,但各自都具有重要意义。 一个缺陷直接暴露了 workspace 级别的资产操作。 第二个缺陷通过复制将上传的资产 UUID 变成了一个可重用的数据提取原语。 这使得整体的安全问题更难以被忽视。 ## 范围验证 我验证的最明显的覆盖影响是: - `WORKSPACE_LOGO` 这是有意为之的,因为它很容易验证,并展示了明显的跨租户完整性失效。 但该 endpoint 并不局限于 workspace logo。 存在漏洞的 workspace 级别资产流程还接受多种实体上下文,包括: - 项目封面 - 用户图像 - 事务内容 - 页面内容 - 评论内容 这很重要,因为它表明该漏洞是**结构性的**,而不是与单一的品牌标识字段绑定。 我直接验证了 workspace logo 路径。 更广泛的代码路径强烈表明,其他由资产支持的上下文也暴露在同样的授权错误中。 ## 严重性和分类 这个问题被合理地归类为 **High**。 咨询分类为: - **CWE-862**: 缺少授权 - **CWE-639**: 通过用户控制键绕过授权 - **CVSS:** ``` CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L ``` 这种分类是有道理的。 这并不是说未经身份验证的攻击者可以从无到有地破坏 Plane。 而是说任何经过身份验证的普通用户都可以在 V2 资产子系统中跨越租户边界,并对其他 workspace 执行高影响的资产操作。 这是一个真实且无可辩驳的多租户授权漏洞。 ## 为什么这仍然值得报告 有些人低估了经过身份验证的跨租户漏洞,因为他们听到: 这不是一个严肃的辩护。 在多 workspace 软件中,经过身份验证的普通用户应该被**限制**在自己的授权范围内。 如果 workspace Bravo 中的一个低权限用户可以读取、复制、删除或覆盖 workspace Alpha 中的对象,那么 workspace 隔离就被破坏了。 这正是应用程序应该保护的安全属性。 特别是在一个存储内部工作内容和品牌资产的项目管理平台中,这是一个具有真实机密性和完整性影响的重要问题。 ## 修复分析 该问题已在 **Plane v1.3.1** 中修复。 `v1.3.1` 的发行说明清楚地描述了该修复: - 为所有 `WorkspaceFileAssetEndpoint` 方法添加 `@allow_permission` - 将 `DuplicateAssetEndpoint` 源资产查找范围限制为调用者是活跃成员的 workspace 这是正确的修复方向,因为它解决了两个失败的安全属性: 1. **workspace 级别的资产操作现在需要真正的成员资格强制执行** 2. **复制流程中的源资产不再仅凭 UUID 被信任** 这正是这个漏洞所需要的。 这里的一个好修复并不是更好地隐藏 UUID。 它不是要更改预签名 URL 生成。 它是为了恢复正确的规则: **在没有限定于当前用户的授权的情况下,workspace slug 加上资产 UUID 绝对不能作为充分条件** 这正是补丁恢复的部分。 ## 披露 该问题是通过 GitHub Security Advisaries 私下报告的。 报告包括: - 两个代码路径的根本原因分析 - 本地的端到端 PoC - 原始 HTTP 证据 - 用于未经授权的下载、复制和覆盖的基于哈希的证明 - 修复指导 该问题后来被发布为: - **GHSA-qw87-v5w3-6vxx** - **CVE-2026-46558** 该咨询发布于 **2026 年 5 月 15 日**。 修复程序在 **Plane v1.3.1** 中提供。 ## 这个漏洞真正教会了我们什么 这里的关键教训很简单: 许多开发人员会从以下角度思考: - 上传成功 - 对象存在 - UUID 解析 - 预签名 URL 有效 这些都是实现细节。 真正的安全问题是: **谁被允许跨租户边界解析、修改、复制或重新链接该资产?** 在 Plane 中,该边界没有得到一致的执行。 这才是真正的结论。 这个漏洞还强化了关于审查多租户应用程序的一些重要观点: - 共享对象层需要直接的安全审查 - 当授权不完整时,攻击者控制的标识符就足够了 - 单个子系统可以同时暴露机密性和完整性失效 ## 关键点 - 资产 endpoint 是真实的多租户安全边界 - 经过身份验证的访问与经过授权的跨 workspace 访问不是一回事 - workspace slug 和资产 UUID 本身永远不应是足够的 - 当上游授权薄弱时,预签名下载生成就会变得危险 - 同时验证读取和写入后果会使授权报告更有说服力 - 共享资产系统中的结构性授权漏洞通常不止一种实体类型 ## 结语 这个漏洞与奇特的存储行为无关。 它是关于提出正确的信任边界问题。 在 Plane 中,一个经过身份验证的用户可以提供另一个 workspace 的 slug 和资产 UUID,而 V2 资产子系统对这些标识符的信任超出了应有的程度。 这就是为什么它成为了 **CVE-2026-46558**。 已在 **Plane v1.3.1** 中修复。
标签:Linux 内核安全, 企业安全, 漏洞分析, 漏洞探索, 网络资产管理, 越权漏洞, 路径探测, 逆向工具, 项目管理