
O subsistema de ativos do Plane V2 confiava em slugs de workspace e UUIDs de ativos sem aplicar as verificações de associação corretas, o que permitia que um usuário autenticado lesse, copiasse, excluísse e sobrescrevesse ativos em outros workspaces.
O subsistema de ativos V2 do Plane confiava em slugs de workspace e UUIDs de ativos sem aplicar as verificações de associação corretas, o que permitia que um usuário autenticado lesse, copiasse, excluísse e sobrescrevesse ativos em outros workspaces.
Encontrei este problema ao revisar o Plane, a plataforma de gerenciamento de projetos de código aberto, com uma pergunta muito específica em mente:
Os endpoints de ativos V2 realmente aplicam limites de workspace ou confiam demais em slugs de workspace e IDs de ativos fornecidos pelo atacante?
Neste caso, a resposta foi não.
O subsistema de ativos V2 do Plane expôs duas falhas de autorização relacionadas que quebraram o isolamento do workspace para qualquer usuário autenticado:
Isso tornou possível o abuso de ativos entre workspaces.
No meu PoC validado, um usuário normal no workspace Bravo foi capaz de:
Esse problema foi posteriormente atribuído CVE-2026-46558.
Plane: Plane no GitHub
CVE: CVE-2026-46558
Isso afetou o Plane, que em seu site oficial é apresentado como sendo usado por mais de 50.000 equipes em todo o mundo. O Plane também destaca forte adoção de código aberto, incluindo mais de 46.000 estrelas no GitHub e mais de 1.000.000 de pulls no Docker, e exibe organizações como Tencent, Accenture, Microsoft e Amazon.
atacante autenticado no workspace B → rota de ativo V2 no nível do workspace confia no slug do workspace de destino e no UUID do ativo sem verificações de associação adequadas → leitura / patch / exclusão pré-assinados contra ativos do workspace A + a pesquisa de fonte de duplicação de ativos confia no UUID de origem carregado → divulgação, cópia, exclusão e sobrescrição de branding entre workspaces
Plane é uma plataforma de gerenciamento de projetos de código aberto usada para gerenciar:
Isso significa que seu subsistema de ativos está em um limite de confiança real.
A questão importante aqui não era se o Plane suporta uploads.
A verdadeira questão era:
O Plane aplica o isolamento do workspace quando um usuário autenticado referencia ativos pertencentes a outro workspace?
Neste caso, não aplicava.
Muitas revisões de aplicativos multi-inquilino focam primeiro em endpoints administrativos óbvios ou atualizações diretas de configurações.
Isso perde uma classe de bug muito comum e muito real:
acesso secundário a objetos por meio de sistemas de arquivos ou ativos compartilhados
Sistemas de ativos são fáceis de errar porque frequentemente combinam:
Esse é exatamente o tipo de lugar onde os limites do inquilino enfraquecem silenciosamente.
Este problema não era sobre corrupção de armazenamento. Não era sobre o S3 em si. Não era sobre manipulação de MIME de upload.
Era uma falha de limite de autorização:
Isso é suficiente para criar uma vulnerabilidade real.
Não abordei o Plane cegamente fuzzeando endpoints aleatórios ou adivinhando UUIDs sem modelo.
A abordagem mais forte foi identificar primeiro o limite de isolamento mais promissor.
Para o Plane, esse era o subsistema de ativos V2.
Por quê?
Porque um sistema de ativos compartilhado se torna perigoso quando:
Esse era o limite certo a inspecionar.
E foi exatamente onde o bug vivia.
Isso era, na verdade, duas falhas de autorização relacionadas no mesmo subsistema.
As rotas de ativos no nível do workspace foram expostas através de:
apps/api/plane/app/urls/asset.py:50-56Os manipuladores vulneráveis estavam em:
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:409O problema era simples.
WorkspaceFileAssetEndpoint aceitava um slug de workspace e um UUID de ativo, depois resolvia objetos diretamente como:
workspace = Workspace.objects.get(slug=slug)
e:
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug)
sem primeiro garantir que o chamador era realmente um membro autorizado daquele workspace de destino.
Isso significava que o endpoint ainda podia:
para objetos de outro workspace.
A rota de duplicação de ativos foi mapeada através de:
apps/api/plane/app/urls/asset.py:100-101A lógica vulnerável estava em:
apps/api/plane/app/views/asset/v2.py:736-780O workspace de destino tinha um decorador de autorização. Mas a consulta do ativo de origem não tinha.
O objeto de origem foi carregado com:
original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first()
Isso significava que o chamador só precisava:
Não havia verificação de que o chamador pertencia ao workspace de origem que realmente possuía aquele ativo.
Esse é todo o segundo bug.
A distinção importante é o impacto entre workspaces.
Muitos bugs de autorização são minimizados como:
"ainda requer login"
Isso perde o ponto.
A verdadeira questão não é:
"O chamador está autenticado?"
A verdadeira questão é:
"O chamador está autorizado para o workspace específico e o ativo específico que está sendo manipulado?"
No Plane, a resposta era não.
Isso transforma o que pode parecer um tratamento comum de objetos em um problema real de segurança multi-inquilino.
Há uma clara diferença entre:
Este problema era firmemente o segundo caso.
Validei o problema localmente contra o Plane Community Edition 1.2.3 usando dois usuários comuns em dois workspaces não relacionados:
alpha-20260323072017bravo-20260323072017Usei Alpha para criar um ativo carregado privado legítimo em um problema de projeto.
O ID do ativo privado validado na minha execução foi:
6ed6ed62-d1b2-4399-8220-336c01b7d72c
Como Bravo, solicitei:
GET /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
O Plane retornou:
HTTP/1.1 302 Found
com uma URL de download pré-assinada para o ativo do Alpha.
O hash do arquivo baixado correspondeu exatamente ao ativo privado original do Alpha:
original: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
leitura não autorizada: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
Isso provou que o caminho de leitura cruzou os limites do workspace com sucesso.
Como Bravo, então solicitei:
POST /api/assets/v2/workspaces/bravo-20260323072017/duplicate-assets/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
O Plane retornou:
HTTP/1.1 200 OK
e criou um ativo duplicado no lado do atacante:
72d51497-ccc1-4546-ba14-28fae5d37dbb
O SHA-256 do arquivo duplicado correspondeu exatamente ao ativo original do Alpha:
0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
Isso provou que apenas o UUID do ativo de origem era suficiente para copiar conteúdo entre workspaces para um workspace controlado pelo atacante.
Como Bravo, então enviei:
DELETE /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
O Plane retornou:
HTTP/1.1 204 No Content
Quando Alpha posteriormente buscou aquele ativo, o servidor retornou:
HTTP/1.1 404 Not Found
Isso provou impacto na integridade entre workspaces, não apenas divulgação.
Como Bravo, criei um ativo WORKSPACE_LOGO contra o workspace do Alpha através da rota vulnerável de ativos no nível do workspace, carreguei conteúdo controlado pelo atacante e finalizei.
Depois disso, os metadados do workspace do Alpha apontavam para o ativo de logotipo controlado pelo atacante:
c1032f06-3cf5-4f7e-b139-e6976d8c567d
O hash do logotipo final baixado correspondeu exatamente à carga do atacante:
esperado: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
observado: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
Isso provou um caminho visível de sobrescrição entre workspaces, não apenas um problema de acesso oculto ao backend.
Qualquer um dos resultados acima já teria sido suficiente para justificar um relatório de bug real.
Mas validar a cadeia completa importou por duas razões.
Mostrou que o problema não estava limitado à exposição somente leitura.
O mesmo limite fraco permitia:
Isso torna o impacto muito mais forte do que um IDOR estreito de "pode buscar um arquivo".
Mostrou que os dois caminhos de código estavam relacionados, mas eram independentemente importantes.
Uma falha expunha diretamente as operações de ativos no nível do workspace. A segunda falha transformava UUIDs de ativos carregados em uma primitiva de exfiltração reutilizável através da duplicação.
Isso tornou a história geral de segurança muito mais difícil de descartar.
O impacto de sobrescrição mais visível que validei foi:
WORKSPACE_LOGOIsso foi deliberado porque é fácil de verificar e demonstra uma falha óbvia de integridade entre inquilinos.
Mas o endpoint não estava limitado a logotipos de workspace.
O fluxo vulnerável de ativos no nível do workspace também aceitava múltiplos contextos de entidade, incluindo:
Isso importava porque mostrava que o bug era estrutural, não vinculado a um único campo de branding.
Validei o caminho do logotipo do workspace diretamente. O caminho de código mais amplo sugeria fortemente que contextos adicionais apoiados por ativos estavam expostos ao mesmo erro de autorização.
Este problema foi razoavelmente classificado como Alta.
A classificação do aviso foi:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L
Essa classificação faz sentido.
A alegação não é que um atacante não autenticado pode comprometer o Plane do nada. A alegação é que qualquer usuário autenticado normal pode cruzar limites de inquilino no subsistema de ativos V2 e realizar operações de alto impacto contra outros workspaces.
Isso é uma vulnerabilidade de autorização multi-inquilino real e defensável.
Algumas pessoas subestimam bugs autenticados entre inquilinos porque ouvem:
"o atacante já precisava de uma conta"
Isso não é uma defesa séria.
Em software multi-workspace, usuários autenticados normais devem estar contidos dentro de seu próprio escopo de autorização.
Se um usuário com baixos privilégios no workspace Bravo pode ler, copiar, excluir ou sobrescrever objetos no workspace Alpha, então o isolamento do workspace está quebrado.
Essa é exatamente a propriedade de segurança que o aplicativo deveria proteger.
Especialmente em uma plataforma de gerenciamento de projetos que armazena conteúdo de trabalho interno e ativos de branding, isso é um problema significativo com impacto real na confidencialidade e integridade.
O problema foi corrigido no Plane v1.3.1.
As notas de lançamento para v1.3.1 descreveram a correção claramente:
@allow_permission a todos os métodos de WorkspaceFileAssetEndpointDuplicateAssetEndpoint para workspaces onde o chamador é um membro ativoEssa é a direção correta de remediação porque aborda ambas as propriedades de segurança falhas:
Isso é exatamente o que este bug precisava.
Uma boa correção aqui não é sobre esconder UUIDs melhor. Não é sobre alterar a geração de URLs pré-assinados.
É sobre restaurar a regra correta:
slug de workspace mais UUID de ativo nunca deve ser suficiente sem autorização com escopo para o usuário atual
Essa é a parte que o patch restaurou.
Este problema foi reportado em particular através do GitHub Security Advisories.
O relatório incluía:
O problema foi posteriormente publicado como:
O aviso foi publicado em 15 de maio de 2026. A correção foi lançada no Plane v1.3.1.
A lição principal aqui é simples:
subsistemas de ativos compartilhados são limites de autorização, não apenas ajudantes de armazenamento
Muitos desenvolvedores pensam em termos de:
Essas coisas são detalhes de implementação.
A verdadeira questão de segurança é:
quem tem permissão para resolver, mutar, copiar ou religar aquele ativo entre limites de inquilinos?
No Plane, esse limite não foi aplicado consistentemente.
Essa é a verdadeira lição.
Este bug também reforça algo importante sobre revisar aplicativos multi-inquilino:
Esta vulnerabilidade não era sobre comportamento exótico de armazenamento.
Era sobre fazer a pergunta certa sobre o limite de confiança.
No Plane, um usuário autenticado poderia fornecer o slug e os UUIDs de ativos de outro workspace, e o subsistema de ativos V2 confiava nesses identificadores mais do que deveria.
É por isso que isso se tornou CVE-2026-46558.
Corrigido no Plane v1.3.1.