Plane 的 V2 资产子系统在缺乏正确成员关系检查的情况下,信任了用户提供的工作区 slug 和资产 UUID,这使得一个已认证用户可以读取、复制、删除和覆盖其他工作区中的资产。
我在评审 Plane(开源项目管理平台)时发现了这个问题,带着一个非常具体的问题:
V2 资产端点是否实际实施了工作区隔离,还是过度信任了攻击者提供的工作区 slug 和资产 ID?
在这个案例中,答案是不。
Plane 的 V2 资产子系统暴露了两个相关的授权缺陷,破坏了任何已认证用户的工作区隔离:
这使得跨工作区资产滥用成为可能。
在我验证的概念验证(PoC)中,一个位于工作区 Bravo 的普通用户能够:
该问题后来被分配了 CVE-2026-46558。
Plane: Plane on GitHub
CVE: CVE-2026-46558
这影响了 Plane,根据其官方网站,该平台被全球 50,000 多个团队 使用。Plane 还强调了其强大的开源采用率,包括 46,000 多个 GitHub Star 和 1,000,000 多次 Docker 拉取,并展示了诸如 腾讯、埃森哲、微软 和 亚马逊 等组织。
已认证的攻击者位于工作区 B → 工作区级 V2 资产路由信任目标工作区 slug 和资产 UUID,但缺少适当的成员关系检查 → 针对工作区 A 资产的预签名读取/补丁/删除操作 + 资产复制来源查找信任上传的 source UUID → 跨工作区泄露、复制、删除和品牌覆盖
Plane 是一个开源项目管理平台,用于管理:
这意味着其资产子系统位于真正的信任边界上。
这里重要的问题并不是 Plane 是否支持上传。
真正的问题是:
当一个已认证用户引用另一个工作区所拥有的资产时,Plane 是否会实施工作区隔离?
在这个案例中,答案是否定的。
许多多租户应用评审首先关注的是明显的管理员端点或直接的设置更新。
这忽略了一个非常常见且真实存在的漏洞类别:
通过共享文件或资产子系统进行次级对象访问
资产系统容易出错,因为它们通常结合了:
这正是租户边界被悄悄削弱的地方。
这个问题并非关于存储损坏。 也不是关于 S3 本身。 更不是关于上传 MIME 类型处理。
它属于 授权边界失败:
这就足以构成一个真正的漏洞。
我没有通过盲目模糊测试随机端点或猜测 UUID 来接近 Plane。
更有效的方法是首先识别最有前景的隔离边界。
对于 Plane 来说,那就是 V2 资产子系统。
为什么?
因为共享资产系统在以下情况下会变得危险:
那正是需要检查的边界。
而漏洞恰恰就存在于那里。
这实际上是同一子系统中的两个相关授权失败。
工作区级资产路由通过以下代码暴露:
apps/api/plane/app/urls/asset.py:50-56漏洞处理程序位于:
apps/api/plane/app/views/asset/v2.py:314apps/api/plane/app/views/asset/v2.py:379apps/api/plane/app/views/asset/v2.py:400apps/api/plane/app/views/asset/v2.py:409问题很简单。
WorkspaceFileAssetEndpoint 接受一个工作区 slug 和资产 UUID,然后直接解析对象,例如:
workspace = Workspace.objects.get(slug=slug)
以及:
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug)
而未首先强制调用者实际是该目标工作区的授权成员。
这意味着端点仍然可以:
针对另一个工作区的对象。
资产复制路由通过以下代码映射:
apps/api/plane/app/urls/asset.py:100-101漏洞逻辑位于:
apps/api/plane/app/views/asset/v2.py:736-780目标工作区有一个授权装饰器。 但来源资产查找却没有。
来源对象使用以下代码加载:
original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first()
这意味着调用者只需要:
没有检查调用者是否属于实际拥有该资产的来源工作区。
这就是第二个漏洞的全部。
重要的区别在于跨工作区的影响。
许多授权错误被最小化为:
“它仍然需要登录”
这忽略了问题的核心。
真正的问题并非:
“调用者是否已认证?”
真正的问题是:
“调用者是否被授权访问具体的工作区和具体的资产?”
在 Plane 中,答案是否定的。
这将看起来像普通对象处理的行为,变成了一个真正的多租户安全问题。
存在明显的区别:
本问题明确属于第二种情况。
我本地在 Plane Community Edition 1.2.3 上针对两个不相关工作区中的两个普通用户验证了该问题:
alpha-20260323072017bravo-20260323072017我使用 Alpha 在一个项目问题中创建了一个合法的私有上传资产。
在我运行中验证的私有资产 ID 是:
6ed6ed62-d1b2-4399-8220-336c01b7d72c
作为 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
这证明读取路径成功跨越了工作区边界。
作为 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 就足以将跨工作区内容复制到攻击者控制的工作区中。
作为 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
这证明了跨工作区的完整性影响,而不仅仅是信息披露。
作为 Bravo,我通过易受攻击的工作区级资产路由,针对 Alpha 的工作区创建了一个 WORKSPACE_LOGO 资产,上传了攻击者控制的内容并完成了资产。
之后,Alpha 的工作区元数据指向了攻击者控制的标志资产:
c1032f06-3cf5-4f7e-b139-e6976d8c567d
下载的最终标志哈希值与攻击者负载完全相同:
expected: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
observed: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
这证明存在可见的跨工作区覆盖路径,而不仅仅是隐秘的后端访问问题。
上述任何一项结果都足以构成一个真正的漏洞报告。
但验证完整的攻击链之所以重要,有两个原因。
它表明问题并不仅限于只读暴露。
相同的薄弱边界导致了:
这使得影响远强于狭义的“可以获取一个文件”的 IDOR。
它表明两条代码路径是相关的,但各自独立重要。
一个缺陷直接暴露了工作区级资产操作。 第二个缺陷将已上传的资产 UUID 变成了通过复制实现的可重用外泄原语。
这使得整体安全故事更难被忽视。
我验证的最明显的覆盖影响是:
WORKSPACE_LOGO这是故意的,因为它易于验证并展示了明显的跨租户完整性失败。
但端点并不限于工作区标志。
易受攻击的工作区级资产流程也接受多种实体上下文,包括:
这很重要,因为它表明该漏洞是 结构性的,而不是局限于单个品牌字段。
我直接验证了工作区标志路径。 更广泛的代码路径强烈表明其他资产支持的上下文也暴露于相同的授权错误中。
该问题被合理分类为 高。
公告分类如下:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L
这个分类是合理的。
声明并非是一个未认证的攻击者可以从零开始攻陷 Plane。 声明是任何普通的已认证用户可以在 V2 资产子系统中跨越租户边界,并对其他工作区执行高影响力的资产操作。
这是一个真实且可辩护的多租户授权漏洞。
有些人低估已认证的跨租户漏洞,因为他们听到:
“攻击者已经需要一个账户了”
这并非一个严肃的辩护。
在多工作区软件中,普通已认证用户应该被 限制 在自己的授权范围内。
如果工作区 Bravo 中的一个低权限用户可以读取、复制、删除或覆盖工作区 Alpha 中的对象,那么工作区隔离就被破坏了。
这正是应用程序应该保护的安全属性。
尤其是在一个存储内部工作内容和品牌资产的项目管理平台中,这是一个具有实际机密性和完整性影响的重大问题。
该问题在 Plane v1.3.1 中得到了修复。
v1.3.1 的发布说明清楚地描述了修复内容:
WorkspaceFileAssetEndpoint 方法添加 @allow_permissionDuplicateAssetEndpoint 的来源资产查找范围限定为调用者是活跃成员的工作区这是正确的修复方向,因为它解决了两个失败的安全属性:
这正是本漏洞所需要的。
一个好的修复并不在于更好地隐藏 UUID。 也不在于更改预签名 URL 的生成。
而在于恢复正确的规则:
工作区 slug 加上资产 UUID 永远不应该在缺少针对当前用户的授权范围时成为充分条件
这就是补丁所恢复的内容。
该问题通过 GitHub 安全公告私下报告。
报告内容包括:
该问题后来被发布为:
公告于 2026年5月15日 发布。 修复于 Plane v1.3.1 中交付。
关键教训很简单:
共享资产子系统是授权边界,而不仅仅是存储辅助工具
许多开发者的思维模式是:
这些是实现细节。
真正的安全问题是:
谁被允许跨租户边界解析、修改、复制或重新链接该资产?
在 Plane 中,这个边界并未得到一致实施。
这才是真正的要点。
这个漏洞也强化了评审多租户应用程序中一个重要方面:
这个漏洞并不涉及奇特的存储行为。
它关乎提出正确的信任边界问题。
在 Plane 中,一个已认证用户可以提供另一个工作区的 slug 和资产 UUID,而 V2 资产子系统对这些标识符的信任超出了应有的限度。
这就是它成为 CVE-2026-46558 的原因。
已在 Plane v1.3.1 中修复。