
Подсистема активов 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 ответ был отрицательным.
Это превращает то, что может выглядеть как обычная обработка объектов, в реальную проблему безопасности мультитенантности.