
L'importation d'images à distance de Penpot permet à un éditeur de fichiers authentifié de transformer une fonctionnalité de commodité multimédia normale en SSRF d'origine serveur, car des URL contrôlées par l'attaquant ont traversé un chemin de récupération de serveur suivant des redirections sans filtrage de destination.
La fonction d'importation d'images distantes de Penpot permettait à un éditeur de fichier authentifié de transformer une fonctionnalité multimédia normale en SSRF d'origine serveur, car des URL contrôlées par l'attaquant tombaient dans un chemin de récupération serveur suivant les redirections, sans filtrage de destination.
J'ai découvert ce problème en examinant Penpot, la plateforme open-source de conception et de collaboration sur le code, avec une question très spécifique en tête :
Que se passe-t-il lorsqu'un outil de conception collaborative permet à un utilisateur de fournir au backend une URL d'image distante à récupérer ?
Dans ce cas, cette question a conduit à un véritable bug.
Le flux d'importation d'images distantes de Penpot acceptait une URL contrôlée par l'utilisateur et faisait récupérer cette URL par le backend depuis le contexte réseau du serveur, sans imposer de restrictions de destination pour les cibles loopback ou réseau privé. Le client HTTP partagé suivait également automatiquement les redirections.
Cela a transformé une fonctionnalité multimédia normale en une primitive SSRF d'origine serveur authentifiée, et est finalement devenu CVE-2026-45806.
Penpot : Penpot sur 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
Cela affectait Penpot. Sur son site officiel et son kit média, Penpot se présente comme ayant une base d'utilisateurs en croissance de plus d'un million et indique que des dizaines de milliers d'organisations l'utilisent, notamment Blender, Mozilla, Fedora, NTT Data, MIT, Société Générale, Cisco, Fujitsu, Indra et ByteDance.
éditeur de fichier authentifié -> URL d'image distante contrôlée par l'attaquant -> create-file-media-object-from-url -> récupération d'image backend download-image avec redirections activées -> requête finale atterrit sur un point de terminaison d'image interne uniquement -> SSRF d'origine serveur / atteignabilité interne
Penpot est une plateforme open-source de conception et de collaboration sur le code.
Elle gère des choses comme :
Cela signifie que son chemin d'importation multimédia se situe sur une véritable frontière de confiance.
La question importante n'était pas de savoir si Penpot prend en charge l'importation d'images distantes.
La vraie question était :
Penpot restreint-il l'endroit où le backend est autorisé à se connecter lorsqu'un utilisateur importe une image distante ?
Dans ce cas, ce n'était pas le cas.
Beaucoup de gens sous-estiment les fonctionnalités d'importation distante.
C'est une erreur.
Dès qu'une application :
elle crée une véritable frontière de confiance sortante.
C'était le problème ici.
Ce bug ne se situait pas dans le rendu d'image. Il ne se situait pas dans le stockage de fichiers. Il ne se situait pas dans les vérifications d'autorisations ordinaires pour l'édition d'un fichier.
C'était un classique échec de confiance côté serveur :
C'est suffisant pour créer une véritable vulnérabilité.
Je n'ai pas abordé Penpot en fuzzeant aveuglément des méthodes RPC aléatoires ou en recherchant d'abord des crashs.
L'approche plus solide consistait à identifier la frontière de sécurité la plus prometteuse.
Pour Penpot, c'était l'importation de médias distants.
Pourquoi ?
Parce que cette fonctionnalité combine :
C'était la bonne frontière à inspecter.
Et c'était exactement là où se trouvait le bug.
Le bug se réduit à une petite chaîne de confiance.
Dans le 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 contrôlée par l'utilisateur est envoyée directement dans l'appel RPC.
Ensuite, dans le 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))])
et :
(defn- create-file-media-object-from-url
[cfg {:keys [url name] :as params}]
(let [content (media/download-image cfg url)
le backend vérifie que l'appelant peut éditer le fichier cible, puis passe l'URL contrôlée par l'attaquant dans media/download-image.
L'implémentation de la récupération est ici :
(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})
Et le client HTTP partagé est configuré comme suit :
(http/build-client {:connect-timeout 30000
:follow-redirects :always}))
C'est toute la vulnérabilité :
Parce que l'attaquant a seulement besoin :
La chaîne d'attaque est simple :
C'est tout le bug.
La distinction importante est où la requête se produit.
La question n'est pas :
"Penpot peut-il importer des images à partir d'URL ?"
La vraie question est :
"Un utilisateur authentifié peut-il faire en sorte que le backend de Penpot se connecte à des destinations internes que l'utilisateur ne devrait pas pouvoir atteindre via l'application ?"
Dans ce cas, la réponse était oui.
Cela compte car il y a une réelle différence entre :
La validation d'image ne supprime pas cette différence.
Elle réduit certains cas d'exfiltration directe, mais elle ne supprime pas la condition SSRF ni la rupture de frontière réseau.