
L'importazione remota di immagini di Penpot ha permesso a un editor di file autenticato di trasformare una normale funzionalità pratica dei media in una SSRF di origine backend poiché URL controllati dall'attaccante sono finiti in un percorso di recupero server che segue i reindirizzamenti senza filtraggio della destinazione.
L'importazione remota di immagini di Penpot ha permesso a un editor di file autenticato di trasformare una normale funzionalità multimediale in una SSRF di origine backend, poiché URL controllati dall'attaccante sono finiti in un percorso di fetch del server che seguiva i redirect senza filtraggio della destinazione.
Ho trovato questo problema mentre analizzavo Penpot, la piattaforma open-source di collaborazione su design e codice, con una domanda molto specifica in mente:
Cosa succede quando uno strumento di design collaborativo permette a un utente di passare al backend un URL di un'immagine remota da scaricare?
In questo caso, quella domanda ha portato a un vero bug.
Il flusso di importazione di immagini remote di Penpot accettava un URL controllato dall'utente e faceva sì che il backend lo scaricasse dal contesto di rete del server senza imporre restrizioni sulla destinazione per target su loopback o rete privata. Il client HTTP condiviso seguiva anche automaticamente i redirect.
Questo ha trasformato una normale funzionalità multimediale in una primitiva SSRF di origine backend autenticata, portando infine a CVE-2026-45806.
Penpot: Penpot su 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
Questo ha interessato Penpot. Sul suo sito ufficiale e nel media kit, Penpot si presenta come avente una base di utenti in crescita di oltre 1 milione e afferma che decine di migliaia di organizzazioni lo usano, tra cui Blender, Mozilla, Fedora, NTT Data, MIT, Société Générale, Cisco, Fujitsu, Indra e ByteDance.
editor di file autenticato -> URL di immagine remota controllato dall'attaccante -> create-file-media-object-from-url -> download-image del backend con redirect abilitati -> richiesta finale atterra su endpoint interno solo immagini -> SSRF di origine backend / raggiungibilità interna
Penpot è una piattaforma open-source di collaborazione su design e codice.
Gestisce cose come:
Ciò significa che il suo percorso di importazione multimediale si trova su un vero confine di fiducia.
La domanda importante qui non era se Penpot supporti l'importazione di immagini remote.
La vera domanda era:
Penpot limita dove il backend può connettersi quando un utente importa un'immagine remota?
In questo caso, non lo faceva.
Molte persone sottovalutano le funzionalità di importazione remota.
È un errore.
Nel momento in cui un'applicazione:
crea un vero confine di fiducia in uscita.
Questo era il problema qui.
Questo bug non riguardava il rendering delle immagini. Non riguardava l'archiviazione dei file. Non riguardava i normali controlli di permesso per la modifica di un file.
Era un classico fallimento di fiducia lato server:
Questo è sufficiente per creare una vera vulnerabilità.
Non ho affrontato Penpot testando ciecamente metodi RPC casuali o cercando crash prima di tutto.
L'approccio più forte era identificare il confine di sicurezza più promettente.
Per Penpot, quello era l'importazione di media remoti.
Perché?
Perché questa funzionalità combina:
Quello era il confine giusto da ispezionare.
Ed era esattamente dove viveva il bug.
Il bug si riduce a una piccola catena di fiducia.
Nel 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}))
l'url controllato dall'utente viene inviato direttamente nella chiamata RPC.
Poi nel 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)
il backend verifica che il chiamante possa modificare il file di destinazione, quindi passa l'URL controllato dall'attaccante in media/download-image.
L'implementazione dello scaricamento è qui:
(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 il client HTTP condiviso è configurato come:
(http/build-client {:connect-timeout 30000
:follow-redirects :always}))
Questa è l'intera vulnerabilità:
Perché l'attaccante ha bisogno solo di:
La catena d'attacco è semplice:
Questo è l'intero bug.
La distinzione importante è dove avviene la richiesta.
La domanda non è:
"Penpot può importare immagini da URL?"
La vera domanda è:
"Un utente autenticato può far sì che il backend di Penpot si connetta a destinazioni interne a cui l'utente non dovrebbe poter accedere tramite l'applicazione?"
In questo caso, la risposta era sì.
Questo è importante perché c'è una differenza reale tra:
La validazione delle immagini non rimuove quella differenza.
Riduce alcuni casi di esfiltrazione diretta, ma non rimuove la condizione SSRF né la violazione del confine di rete.
Ho validato questo problema con una prova locale controllata direttamente collegata al percorso di codice esaminato di Penpot.
L'obiettivo non era colpire infrastrutture di terze parti. L'obiettivo era provare l'esatta proprietà di sicurezza:
Ho costruito un validatore Java autonomo che rispecchiava il comportamento rilevante:
content-type e content-lengthHo validato due casi.
Il validatore ha richiesto:
http://127.0.0.1:7790/internal.png
Risultato osservato:
http://127.0.0.1:7790/internal.pnghttp://127.0.0.1:7790/internal.png200image/pngCiò ha dimostrato che la logica di scaricamento in stile importazione accettava direttamente un endpoint immagini solo interno.
Il validatore ha quindi richiesto:
http://localhost:7791/redirect-to-internal
Quell'endpoint restituiva un redirect HTTP verso:
http://127.0.0.1:7790/internal.png
Risultato osservato:
http://localhost:7791/redirect-to-internalhttp://127.0.0.1:7790/internal.png200image/pngIl listener solo interno ha registrato la richiesta reindirizzata.
Ciò ha dimostrato l'affermazione più importante:
Il payload qui era intenzionalmente semplice:
Questo era importante perché Penpot non si limita a scaricare byte arbitrari e fermarsi. Esegue una validazione orientata ai media dopo la richiesta.
Quindi la prova giusta non era:
"il backend può provare a connettersi da qualche parte"
La prova più forte era:
"il backend può essere indotto a connettersi da qualche parte all'interno e completare la richiesta con successo sotto gli stessi vincoli simili a immagini che la funzionalità si aspetta"
Questo è esattamente ciò che la validazione ha dimostrato.
Una reazione comune ai bug SSRF come questo è:
"il target deve comunque restituire un'immagine"
Questa osservazione è vera ma incompleta.
Non rimuove la vulnerabilità.
Ti dice solo quali target interni sono più direttamente utili.
Questo problema abilita comunque:
Questa è comunque una vera violazione del confine di sicurezza.
Specialmente in ambienti self-hosted, i servizi interni spesso esistono specificamente dietro quel confine.
A questo problema è stata infine assegnata una gravità Alta secondo CVSS:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Quella classificazione ha senso.
L'affermazione non è che un attaccante non autenticato possa compromettere istantaneamente ogni distribuzione Penpot dal nulla.
L'affermazione è che qualsiasi normale editor di file autenticato può trasformare Penpot in una primitiva di richiesta backend verso destinazioni interne, incluso l'accesso assistito da redirect a target su loopback e rete privata.
C'è stata una discussione sulla gravità durante la divulgazione, principalmente su:
Sono vincoli legittimi da discutere.
Ma non rimuovono il problema centrale:
Questa è una vulnerabilità SSRF reale e difendibile.
La corretta importante qui non è una gestione MIME più rigorosa.
La vera corretta è una politica di destinazione in uscita.
Una corretta rimozione per questa classe di bug deve:
http e httpslocalhostQuesta è la giusta direzione di correzione perché questo non era un bug di parsing delle immagini. Era un bug di confine di fiducia di rete.
Questo problema è stato segnalato privatamente tramite il flusso di segnalazione di sicurezza di GitHub.
Il rapporto includeva:
I manutentori hanno confermato il problema e hanno iniziato a lavorare a una risoluzione.
Al problema è stato successivamente assegnato:
CVE-2026-45806
La lezione chiave qui è semplice:
l'importazione remota di media è un confine di fiducia in uscita, non solo una funzionalità di comodità
Molti sviluppatori pensano in termini di:
Questi sono dettagli implementativi.
La vera domanda di sicurezza è:
dove può connettersi il backend per conto di un utente?
Se quella domanda non viene risposta esplicitamente, funzionalità come l'importazione remota diventano superfici SSRF di default.
Questo bug rafforza anche qualcosa di importante sulla revisione delle SSRF:
Questo è il vero insegnamento.
Questa vulnerabilità non riguardava un payload appariscente.
Si trattava di porre la giusta domanda sul confine di fiducia.
Penpot permetteva a un editor di file autenticato di fornire un URL di immagine remota, e il backend si fidava di quell'URL più di quanto avrebbe dovuto. La gestione dei redirect ha fatto il resto.
Ecco perché questo è diventato CVE-2026-45806.