
A importação remota de imagens do Penpot permite que um editor de arquivos autenticado transforme um recurso normal de conveniência de mídia em SSRF de origem de backend, porque URLs controladas pelo atacante cruzaram para um caminho de busca de servidor que segue redirecionamentos sem filtragem de destino.
A importação remota de imagens do Penpot permitia que um editor de arquivos autenticado transformasse um recurso comum de mídia em um SSRF de origem do backend, porque URLs controladas pelo atacante cruzavam para um caminho de busca do servidor que seguia redirecionamentos sem filtragem de destino.
Encontrei esse problema ao revisar o Penpot, a plataforma de design e colaboração de código aberto, com uma pergunta muito específica em mente:
O que acontece quando uma ferramenta de design colaborativo permite que um usuário entregue ao backend uma URL de imagem remota para buscar?
Neste caso, essa pergunta levou a um bug real.
O fluxo de importação remota de imagens do Penpot aceitava uma URL controlada pelo usuário e fazia com que o backend a buscasse a partir do contexto de rede do servidor, sem impor restrições de destino para alvos de loopback ou rede privada. O cliente HTTP compartilhado também seguia redirecionamentos automaticamente.
Isso transformou um recurso comum de mídia em uma primitiva SSRF autenticada de origem do backend e, por fim, tornou-se CVE-2026-45806.
Penpot: Penpot no GitHub
CVE: CVE-2026-45806
CVSS: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Isso afetou o Penpot. Em seu site oficial e kit de mídia, o Penpot afirma ter uma base de usuários em crescimento de +1M e diz que dezenas de milhares de organizações o utilizam, incluindo Blender, Mozilla, Fedora, NTT Data, MIT, , , , e .

editor de arquivos autenticado -> URL de imagem remota controlada pelo atacante -> create-file-media-object-from-url -> fetch download-image do backend com redirecionamentos habilitados -> requisição final chega a um endpoint de imagem exclusivo interno -> SSRF de origem do backend / alcançabilidade interna
Penpot é uma plataforma de design e colaboração de código aberto.
Ele lida com coisas como:
Isso significa que seu caminho de importação de mídia está em um limite de confiança real.
A pergunta importante aqui não era se o Penpot suporta importar imagens remotas.
A verdadeira pergunta era:
O Penpot restringe onde o backend pode se conectar quando um usuário importa uma imagem remota?
Neste caso, não.
Muitas pessoas subestimam os recursos de importação remota.
Isso é um erro.
No momento em que uma aplicação:
ela cria um limite de confiança de saída real.
Esse era o problema aqui.
Esse bug não estava na renderização de imagens. Não estava no armazenamento de arquivos. Não estava nas verificações de permissão comuns para editar um arquivo.
Era uma clássica falha de confiança no lado do servidor:
Isso é suficiente para criar uma vulnerabilidade real.
Eu não abordei o Penpot fazendo fuzzing cego de métodos RPC aleatórios ou procurando por crashes primeiro.
A abordagem mais forte foi identificar o limite de segurança mais promissor.
Para o Penpot, isso era a importação remota de mídia.
Por quê?
Porque esse recurso combina:
Esse era o limite certo para inspecionar.
E foi exatamente onde o bug residia.
O bug se resume a uma pequena cadeia de confiança.
No frontend:
(defn upload-media-url
[name file-id url]
(rp/cmd!
:create-file-media-object-from-url
{:name name
:file-id file-id
:url url
:is-local true}))
a url controlada pelo usuário é enviada diretamente para a chamada RPC.
Em seguida, no backend:
(sv/defmethod ::create-file-media-object-from-url
...
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(files/check-edition-permissions! pool profile-id file-id)
...
(let [_ (files/get-minimal-file cfg file-id)
mobj (create-file-media-object-from-url cfg (assoc params :profile-id profile-id))])
e:
(defn- create-file-media-object-from-url
[cfg {:keys [url name] :as params}]
(let [content (media/download-image cfg url)
o backend verifica que o chamador pode editar o arquivo de destino e, em seguida, passa a URL controlada pelo atacante para media/download-image.
A implementação do fetch está aqui:
(defn download-image
"Download an image from the provided URI and return the media input object"
[{:keys [::http/client]} uri]
...
(http/req! client
{:method :get :uri uri}
{:response-type :input-stream})
E o cliente HTTP compartilhado está configurado como:
(http/build-client {:connect-timeout 30000
:follow-redirects :always}))
Essa é toda a vulnerabilidade:
Porque o atacante precisa apenas de:
A cadeia de ataque é direta:
Esse é todo o bug.
A distinção importante é onde a requisição ocorre.
A pergunta não é:
"O Penpot pode importar imagens de URLs?"
A verdadeira pergunta é:
"Um usuário autenticado pode fazer o backend do Penpot se conectar a destinos internos que o usuário não deveria conseguir alcançar através da aplicação?"
Neste caso, a resposta foi sim.
Isso importa porque há uma diferença real entre:
A validação de imagem não remove essa diferença.
Ela estreita alguns casos de exfiltração direta, mas não remove a condição de SSRF nem a quebra do limite de rede.
Validei esse problema com uma prova local controlada diretamente ligada ao caminho de código revisado do Penpot.
O objetivo não era atingir infraestrutura de terceiros. O objetivo era provar a propriedade de segurança exata:
Construí um validador Java autocontido que espelhava o comportamento relevante:
content-type e content-lengthValidei dois casos.
O validador solicitou:
http://127.0.0.1:7790/internal.png
Resultado observado:
http://127.0.0.1:7790/internal.pnghttp://127.0.0.1:7790/internal.png200image/pngIsso provou que a lógica de fetch estilo importação aceitou diretamente um endpoint de imagem exclusivo interno.
O validador então solicitou:
http://localhost:7791/redirect-to-internal
Esse endpoint retornou um redirecionamento HTTP para:
http://127.0.0.1:7790/internal.png
Resultado observado:
http://localhost:7791/redirect-to-internalhttp://127.0.0.1:7790/internal.png200image/pngO ouvinte exclusivo interno registrou a requisição redirecionada.
Isso provou a alegação mais importante:
O payload aqui foi intencionalmente simples:
Isso importava porque o Penpot não apenas busca bytes arbitrários e para. Ele realiza validação orientada a mídia após a requisição.
Portanto, a prova correta não era:
"o backend pode tentar se conectar a algum lugar"
A prova mais forte era:
"o backend pode ser levado a se conectar a algum lugar interno e concluir a requisição com sucesso sob as mesmas restrições semelhantes a imagem que o recurso espera"
Isso é exatamente o que a validação demonstrou.
Uma reação comum a bugs SSRF como este é:
"o alvo ainda precisa retornar uma imagem"
Essa observação é verdadeira, mas incompleta.
Ela não remove a vulnerabilidade.
Ela apenas indica quais alvos internos são mais diretamente úteis.
Esse problema ainda permite:
Isso ainda é uma quebra real de limite de segurança.
Especialmente em ambientes auto-hospedados, serviços internos muitas vezes existem especificamente atrás desse limite.
Esse problema foi finalmente atribuído a uma gravidade Alta no CVSS:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Essa classificação faz sentido.
A alegação não é que um atacante não autenticado pode comprometer instantaneamente todas as implantações do Penpot a partir do nada.
A alegação é que qualquer editor de arquivos autenticado normal pode transformar o Penpot em uma primitiva de requisição de backend contra destinos internos, incluindo acesso assistido por redirecionamento a alvos de loopback e rede privada.
Houve alguma discussão sobre gravidade durante a divulgação, principalmente em torno de:
Essas são restrições justas para discutir.
Mas elas não removem o problema central:
Isso é uma vulnerabilidade SSRF real e defensável.
A correção importante aqui não é um tratamento mais rigoroso de MIME.
A verdadeira correção é a política de destino de saída.
Uma remediação correta para essa classe de bug precisa:
http e httpslocalhostEssa é a direção correta da correção porque isso não era um bug de análise de imagem. Era um bug de limite de confiança de rede.
Esse problema foi reportado privadamente através do fluxo de relatórios de segurança do GitHub.
O relatório incluiu:
Os mantenedores confirmaram o problema e começaram a trabalhar em uma resolução.
O problema foi posteriormente atribuído:
CVE-2026-45806
A lição principal aqui é simples:
importação remota de mídia é um limite de confiança de saída, não apenas um recurso de conveniência
Muitos desenvolvedores pensam em termos de:
Esses são detalhes de implementação.
A verdadeira questão de segurança é:
onde o backend tem permissão para se conectar em nome de um usuário?
Se essa pergunta não for respondida explicitamente, recursos como importação remota se tornam superfícies SSRF por padrão.
Esse bug também reforça algo importante sobre a revisão de SSRF:
Essa é a verdadeira lição.
Essa vulnerabilidade não era sobre um payload chamativo.
Era sobre fazer a pergunta certa sobre o limite de confiança.
O Penpot permitia que um editor de arquivos autenticado fornecesse uma URL de imagem remota, e o backend confiava nessa URL mais do que deveria. O tratamento de redirecionamento fez o resto.
É por isso que isso se tornou CVE-2026-45806.