
Подсистема активов V2 в Plane доверяла слагам рабочих пространств и UUID активов без проверки прав членства, что позволяло одному аутентифицированному пользователю читать, копировать, удалять и перезаписывать активы в других рабочих пространствах.
Подсистема V2 ресурсов Plane доверяла слотам рабочих пространств и UUID ресурсов, не выполняя необходимые проверки членства, что позволяло одному аутентифицированному пользователю читать, копировать, удалять и перезаписывать ресурсы в других рабочих пространствах.
Я обнаружил эту проблему при аудите Plane, платформы управления проектами с открытым исходным кодом, руководствуясь очень конкретным вопросом:
Действительно ли конечные точки V2 для ресурсов соблюдают границы рабочих пространств, или же они слишком сильно доверяют предоставленным атакующим слотам и идентификаторам ресурсов?
В данном случае ответ был отрицательным.
Подсистема V2 ресурсов Plane содержала две связанные ошибки авторизации, которые нарушали изоляцию рабочих пространств для любого аутентифицированного пользователя:
Это сделало возможным неправомерное использование ресурсов между рабочими пространствами.
В моем подтвержденном PoC один обычный пользователь в рабочем пространстве Bravo смог:
Этой проблеме позже был присвоен номер CVE-2026-46558.
Plane: Plane на GitHub
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 загружаемых файлов.
Это был сбой границы авторизации:
Этого достаточно для создания реальной уязвимости.
Я не подходил к Plane, слепо фаззя случайные конечные точки или угадывая UUID без модели.
Более сильным подходом было сначала определить наиболее перспективную границу изоляции.
Для 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-20260323072017Я использовал Alpha, чтобы создать легитимный частный загруженный ресурс в проблеме проекта.
Подтвержденный идентификатор частного ресурса в моем запуске был:
6ed6ed62-d1b2-4399-8220-336c01b7d72c
В роли Bravo я запросил:
GET /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
Plane ответил:
HTTP/1.1 302 Found
с предварительно подписанным URL для загрузки ресурса Alpha.
Хэш загруженного файла точно соответствовал оригинальному частному ресурсу 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 я создал ресурс WORKSPACE_LOGO для рабочего пространства Alpha через уязвимый маршрут ресурсов на уровне рабочего пространства, загрузил содержимое, контролируемое атакующим, и финализировал его.
После этого метаданные рабочего пространства 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 четко описывают исправление:
@allow_permission ко всем методам WorkspaceFileAssetEndpointDuplicateAssetEndpoint только теми рабочими пространствами, в которых вызывающий абонент является активным участникомЭто правильное направление исправления, потому что оно устраняет оба нарушенных свойства безопасности:
Именно это и было нужно этой ошибке.
Хорошее исправление здесь — не в том, чтобы лучше скрывать UUID. Не в изменении генерации предварительно подписанных URL.
Оно заключается в восстановлении правильного правила:
слот рабочего пространства плюс UUID ресурса никогда не должны быть достаточными без авторизации, ограниченной текущим пользователем
Это часть, которую исправил патч.
Эта проблема была сообщена конфиденциально через GitHub Security Advisories.
Отчет включал:
Позже проблема была опубликована как:
Консультативное заключение было опубликовано 15 мая 2026 года. Исправление было выпущено в Plane v1.3.1.
Ключевой урок здесь прост:
общие подсистемы ресурсов — это границы авторизации, а не просто вспомогательные средства для хранения
Многие разработчики мыслят в терминах:
Эти вещи — детали реализации.
Настоящий вопрос безопасности:
кому разрешено разрешать, изменять, копировать или перепривязывать этот ресурс между границами арендаторов?
В Plane эта граница не соблюдалась последовательно.
Вот настоящий вывод.
Эта ошибка также подкрепляет важную вещь при проверке мультитенантных приложений:
Эта уязвимость была связана не с экзотическим поведением хранилища.
Она была связана с постановкой правильного вопроса о границе доверия.
В Plane один аутентифицированный пользователь мог указать слот и UUID ресурсов другого рабочего пространства, и подсистема V2 ресурсов доверяла этим идентификаторам больше, чем следовало.
Вот почему это стало CVE-2026-46558.
Исправлено в Plane v1.3.1.