
Plane의 V2 자산 하위 시스템은 올바른 멤버십 확인을 적용하지 않고 신뢰하는 workspace slugs와 asset UUIDs를 사용하여, 인증된 사용자가 다른 워크스페이스의 자산을 읽고, 복사하고, 삭제하고, 덮어쓸 수 있게 했습니다.
Plane의 V2 에셋 하위 시스템은 올바른 멤버십 확인을 시행하지 않고 신뢰된 작업공간 슬러그(slug)와 에셋 UUID를 사용하여, 인증된 사용자가 다른 작업공간의 에셋을 읽고, 복사하고, 삭제하고, 덮어쓸 수 있도록 했습니다.
이 문제는 Plane(오픈소스 프로젝트 관리 플랫폼)을 검토하면서 특정 질문을 염두에 두고 발견했습니다:
V2 에셋 엔드포인트가 실제로 작업공간 경계를 강제하는가, 아니면 공격자가 제공한 작업공간 슬러그와 에셋 ID를 너무 신뢰하는가?
이 경우, 답은 '아니오'였습니다.
Plane의 V2 에셋 하위 시스템은 인증된 모든 사용자에 대해 작업공간 격리를 무너뜨리는 두 가지 관련 인가 결함을 노출했습니다:
이로 인해 작업공간 간 에셋 남용이 가능해졌습니다.
검증된 PoC에서, Bravo 작업공간의 일반 사용자 한 명이 다음을 수행할 수 있었습니다:
이 문제는 이후 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 에셋 하위 시스템이었습니다.
왜냐하면?
공유 에셋 시스템은 다음과 같은 경우 위험해집니다:
그것이 검사할 올바른 경계였습니다.
그리고 버그가 정확히 그곳에 있었습니다.
이것은 실제로 동일한 하위 시스템의 두 가지 관련 인가 실패였습니다.
작업공간 수준 에셋 경로는 다음을 통해 노출되었습니다:
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()
즉, 호출자에게 필요한 것은:
해당 에셋을 실제로 소유한 소스 작업공간에 속하는지 확인하는 검사는 없었습니다.
이것이 두 번째 버그 전부입니다.
중요한 차이는 작업공간 간 영향입니다.
많은 인가 버그는 다음과 같이 최소화됩니다:
"여전히 로그인이 필요함"
그것은 요점을 놓칩니다.
진짜 질문은:
"호출자가 인증되었는가?"
가 아닙니다.
진짜 질문은:
"호출자가 특정 작업공간과 작업 중인 특정 에셋에 대해 인가되었는가?"
입니다.
Plane에서 그 대답은 '아니오'였습니다.
그것은 평범한 객체 처리처럼 보일 수 있는 것을 실제 멀티 테넌트 보안 문제로 만듭니다.
다음 사이에는 명확한 차이가 있습니다:
이 문제는 확실히 두 번째 경우였습니다.
나는 Plane Community Edition 1.2.3에서 두 개의 관련 없는 작업공간에 있는 두 명의 일반 사용자를 사용하여 로컬에서 문제를 검증했습니다:
alpha-20260323072017bravo-20260323072017Alpha를 사용하여 프로젝트 이슈에 합법적인 비공개 업로드 에셋을 만들었습니다.
내 실행에서 검증된 비공개 에셋 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였습니다. 이는 확인이 쉽고 명백한 교차 테넌트 무결성 실패를 보여주기 때문에 의도적이었습니다.
그러나 엔드포인트는 작업공간 로고에 국한되지 않았습니다.
취약한 작업공간 수준 에셋 흐름은 여러 엔티티 컨텍스트도 허용했습니다:
이는 버그가 구조적이며 단일 브랜딩 필드에 묶이지 않음을 보여주기 때문에 중요했습니다.
나는 작업공간 로고 경로를 직접 검증했습니다. 더 넓은 코드 경로는 추가 에셋 지원 컨텍스트가 동일한 인가 실수에 노출되었을 가능성이 높음을 강력히 시사했습니다.
이 문제는 합리적으로 높음(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 소스 에셋 조회를 호출자가 활성 구성원인 작업공간으로 범위 제한이는 올바른 수정 방향입니다. 두 가지 실패한 보안 속성을 모두 해결하기 때문입니다:
이것이 이 버그에 정확히 필요한 것이었습니다.
여기서 좋은 수정은 UUID를 더 잘 숨기는 것이 아닙니다. 사전 서명된 URL 생성을 변경하는 것이 아닙니다.
올바른 규칙을 복원하는 것입니다:
작업공간 슬러그와 에셋 UUID는 현재 사용자로 범위가 지정된 인가 없이는 결코 충분해서는 안 됩니다.
그것이 패치가 복원한 부분입니다.
이 문제는 GitHub Security Advisories를 통해 비공개로 보고되었습니다.
보고서에는 다음이 포함되었습니다:
이 문제는 이후 다음으로 게시되었습니다:
권고는 2026년 5월 15일에 게시되었습니다. 수정 사항은 Plane v1.3.1에서 제공되었습니다.
여기서 핵심 교훈은 간단합니다:
공유 에셋 하위 시스템은 단순한 저장소 도우미가 아니라 인가 경계입니다.
많은 개발자는 다음과 같이 생각합니다:
이러한 것들은 구현 세부 사항입니다.
실제 보안 질문은:
누가 해당 에셋을 테넌트 경계를 넘어 해석, 변형, 복사 또는 재연결할 수 있습니까?
Plane에서는 그 경계가 일관되게 강제되지 않았습니다.
그것이 진정한 핵심입니다.
이 버그는 또한 멀티 테넌트 애플리케이션 검토에 대해 중요한 점을 강화합니다:
이 취약점은 이국적인 저장소 동작에 관한 것이 아니었습니다.
올바른 신뢰 경계 질문을 하는 것에 관한 것이었습니다.
Plane에서 인증된 한 사용자가 다른 작업공간의 슬러그와 에셋 UUID를 제공할 수 있었고, V2 에셋 하위 시스템은 이러한 식별자를 너무 멀리 신뢰했습니다.
그렇기 때문에 이것이 CVE-2026-46558이 되었습니다.
Plane v1.3.1에서 수정되었습니다.