
PlaneのV2アセットサブシステムは、適切なメンバーシップチェックを実施せずにワークスペーススラッグとアセットUUIDを信頼していたため、認証されたユーザーが他のワークスペース内のアセットを読み取り、コピー、削除、上書きすることが可能でした。
PlaneのV2アセットサブシステムは、信頼されたワークスペーススラッグとアセットUUIDを正しいメンバーシップチェックなしで受け入れてしまい、認証されたユーザーが他のワークスペースのアセットを読み取り、コピー、削除、上書きできるようになっていました。
この問題は、オープンソースのプロジェクト管理プラットフォーム Plane をレビューしているときに、非常に具体的な疑問を持って発見しました:
V2アセットエンドポイントは実際にワークスペースの境界を強制しているのか、それとも攻撃者が提供するワークスペーススラッグとアセットIDを信頼しすぎているのか?
このケースでは、答えは「いいえ」でした。
PlaneのV2アセットサブシステムは、認証されたユーザーに対してワークスペースの分離を破る、2つの関連する認可の欠陥を露呈させました:
これにより、ワークスペースをまたがったアセットの悪用が可能になりました。
検証済みのPoCでは、ワークスペースBravoに属する通常のユーザー1人が以下のことを実行できました:
この問題は後に CVE-2026-46558 として割り当てられました。
Plane: GitHub上のPlane
CVE: CVE-2026-46558
影響を受けたのは Plane で、公式サイトでは 世界中の50,000以上のチーム に使用されていると紹介されています。また、Planeは 46,000以上のGitHubスター と 1,000,000以上のDockerプル を誇る強力なオープンソースの採用実績を示しており、Tencent、Accenture、Microsoft、Amazon などの組織で活用されています。
ワークスペースBの認証済み攻撃者 → ワークスペースレベルのV2アセットルートがターゲットワークスペーススラッグとアセットUUIDを適切なメンバーシップチェックなしで信頼 → ワークスペースAのアセットに対する署名付き読み取り/パッチ/削除 + アセット複製のソース検索がアップロードされたソースUUIDを信頼 → ワークスペース間の情報漏洩、コピー、削除、ブランディングの上書き
Plane は、以下の管理に使用されるオープンソースのプロジェクト管理プラットフォームです:
つまり、そのアセットサブシステムは実際の信頼境界上に位置します。
ここで重要な質問は、Planeがアップロードをサポートしているかどうかではありません。
本当の質問はこうです:
認証されたユーザーが別のワークスペースが所有するアセットを参照した場合、Planeはワークスペースの分離を強制するか?
このケースでは、強制していませんでした。
多くのマルチテナントアプリケーションのレビューでは、最初に明白な管理エンドポイントや直接的な設定更新に焦点が当てられます。
しかし、それによって非常に一般的で現実的なバグクラスを見逃します:
共有ファイルまたはアセットサブシステムを介したセカンダリオブジェクトアクセス
アセットシステムは、以下の要素を組み合わせることが多いため、間違えやすいのです:
まさにこれが、テナント境界が静かに弱くなる場所です。
この問題はストレージの破損についてではありません。 S3そのものについてでもありません。 アップロードのMIME処理についてでもありません。
これは 認可境界の失敗 でした:
これだけで現実の脆弱性となります。
私は、盲目的にランダムなエンドポイントをファジングしたり、モデルなしでUUIDを推測したりする方法でPlaneにアプローチしたわけではありません。
より強力なアプローチは、まず最も有望な分離境界を特定することでした。
Planeにとって、それは V2アセットサブシステム でした。
なぜか?
共有アセットシステムは、以下の場合に危険になります:
それが調査するのに適切な境界でした。
そして、まさにバグが存在した場所でした。
これは実際には同じサブシステム内の2つの関連する認可の欠陥でした。
ワークスペースレベルのアセットルートは、以下を通じて公開されていました:
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 はワークスペーススラッグとアセット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()
つまり、呼び出し元に必要なのは以下だけでした:
そのアセットを実際に所有するソースワークスペースに呼び出し元が属しているかどうかのチェックはありませんでした。
これが2つ目のバグ全体です。
重要な違いは、ワークスペースをまたがった影響です。
多くの認可バグは次のように軽視されます:
「それでもログインは必要だ」
それはポイントを見逃しています。
本当の質問は次の通りです:
「呼び出し元は認証されているか?」
本当の質問は次の通りです:
「呼び出し元は、操作対象の特定のワークスペースと特定のアセットに対して認可されているか?」
Planeでは、その答えは「いいえ」でした。
これにより、普通のオブジェクト処理のように見えるものが、実際のマルチテナントセキュリティ問題に変わります。
以下の間には明確な違いがあります:
この問題は後者に確固として該当しました。
私は Plane Community Edition 1.2.3 に対して、2つの無関係なワークスペースに属する2人の通常ユーザーを使用してローカルで問題を検証しました:
alpha-20260323072017)bravo-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
これにより、隠れたバックエンドアクセスの問題だけでなく、目に見えるワークスペース間の上書きパスが証明されました。
上記の結果のいずれか1つだけでも、正当なバグレポートとして十分でした。
しかし、完全なチェーンを検証したことには2つの理由がありました。
問題が読み取り専用の露出に限定されないことを示しました。
同じ弱い境界により、以下が可能になりました:
これにより、影響は「1つのファイルを取得できる」という狭いIDORよりもはるかに強力になります。
2つのコードパスが関連しているが独立して重要であることを示しました。
1つの欠陥により、ワークスペースレベルのアセット操作が直接露出しました。 2つ目の欠陥により、アップロードされたアセットUUIDが複製を通じて再利用可能な流出プリミティブに変わりました。
これにより、全体的なセキュリティストーリーを無視することがはるかに難しくなりました。
私が検証した最も目に見える上書きの影響は次のとおりです:
WORKSPACE_LOGOこれは意図的で、確認が容易であり、明確なテナント間の完全性の失敗を示すためです。
しかし、エンドポイントはワークスペースロゴに限定されていませんでした。
脆弱なワークスペースレベルのアセットフローは、複数のエンティティコンテキストも受け入れていました。以下を含みます:
これは、バグが 構造的 であり、単一のブランディングフィールドに結びついていないことを示すため重要です。
私はワークスペースロゴパスを直接検証しました。 より広範なコードパスは、追加のアセットバックアップコンテキストが同じ認可ミスの影響を受けている可能性が高いことを強く示唆しました。
この問題は合理的に High に分類されました。
勧告の分類は次のとおりです:
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_permission を追加DuplicateAssetEndpoint のソースアセットルックアップを、呼び出し元がアクティブメンバーであるワークスペースに限定これは正しい改善方向です。なぜなら、2つの失敗したセキュリティ特性の両方に対処しているからです:
これはまさにこのバグに必要なものでした。
ここでの良い修正は、UUIDをよりよく隠すことではありません。 署名付きURLの生成を変更することでもありません。
正しいルールを復元することです:
ワークスペーススラッグとアセットUUIDだけでは、現在のユーザーにスコープされた認可がなければ決して十分であってはならない
それがパッチが復元した部分です。
この問題は、GitHub Security Advisoriesを通じて非公開で報告されました。
報告には以下が含まれていました:
この問題は後に以下として公開されました:
勧告は 2026年5月15日 に公開されました。 修正は Plane v1.3.1 で出荷されました。
ここでの重要な教訓は単純です:
共有アセットサブシステムは、ストレージヘルパーではなく、認可境界である
多くの開発者は次のように考えます:
これらは実装の詳細です。
本当のセキュリティの質問は次のとおりです:
誰がテナント境界を越えてそのアセットを解決、変更、コピー、または再リンクすることを許可されているか?
Planeでは、その境界は一貫して強制されていませんでした。
それが本当の教訓です。
このバグは、マルチテナントアプリケーションのレビューに関する重要な点も強化します:
この脆弱性は、エキゾチックなストレージ動作に関するものではありません。
それは、正しい信頼境界の質問をすることに関するものでした。
Planeでは、認証された1人のユーザーが別のワークスペースのスラッグとアセットUUIDを提供でき、V2アセットサブシステムはそれらの識別子を必要以上に信頼していました。
それが CVE-2026-46558 になった理由です。
Plane v1.3.1 で修正されました。