
Penpot's remote image import let an authenticated file editor turn a normal media convenience feature into backend-origin SSRF because attacker-controlled URLs crossed into a redirect-following server fetch path without destination filtering.
La importación remota de imágenes de Penpot permitía que un editor de archivos autenticado convirtiera una función de medios normal en SSRF de origen de backend porque las URL controladas por el atacante cruzaban hacia una ruta de búsqueda del servidor que seguía redirecciones sin filtrar destinos.
Encontré este problema al revisar Penpot, la plataforma de diseño y colaboración de código abierto, con una pregunta muy específica en mente:
¿Qué sucede cuando una herramienta de diseño colaborativo permite que un usuario entregue al backend una URL de imagen remota para obtenerla?
En este caso, esa pregunta condujo a un error real.
El flujo de importación de imágenes remotas de Penpot aceptaba una URL controlada por el usuario y hacía que el backend la obtuviera desde el contexto de la red del servidor sin imponer restricciones de destino para objetivos de loopback o red privada. El cliente HTTP compartido también seguía las redirecciones automáticamente.
Eso convirtió una función de medios normal en una primitiva SSRF autenticada de origen de backend y finalmente se convirtió en CVE-2026-45806.
Penpot: Penpot en 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
Esto afectó a Penpot. En su sitio web oficial y kit de medios, Penpot se presenta con una base de usuarios en crecimiento de +1M y dice que decenas de miles de organizaciones lo usan, incluyendo Blender, Mozilla, Fedora, NTT Data, MIT, Société Générale, Cisco, Fujitsu, Indra y ByteDance.
editor de archivos autenticado -> URL de imagen remota controlada por el atacante -> create-file-media-object-from-url -> descarga de imagen del backend con redirecciones habilitadas -> solicitud final llega a un endpoint interno solo de imágenes -> SSRF de origen de backend / alcance interno
Penpot es una plataforma de diseño y colaboración de código abierto.
Maneja cosas como:
Eso significa que su ruta de importación de medios se encuentra en un límite de confianza real.
La pregunta importante aquí no era si Penpot admite la importación de imágenes remotas.
La verdadera pregunta era:
¿Restringe Penpot a dónde puede conectarse el backend cuando un usuario importa una imagen remota?
En este caso, no lo hacía.
Mucha gente subestima las funciones de importación remota.
Eso es un error.
En el momento en que una aplicación:
crea un límite de confianza de salida real.
Ese era el problema aquí.
Este error no estaba en el renderizado de imágenes. No estaba en el almacenamiento de archivos. No estaba en las comprobaciones de permisos habituales para editar un archivo.
Era una falla de confianza del lado del servidor clásica:
Eso es suficiente para crear una vulnerabilidad real.
No abordé Penpot lanzando RPC aleatorios de forma ciega o buscando caídas primero.
El enfoque más sólido fue identificar el límite de seguridad más prometedor.
Para Penpot, ese era la importación de medios remotos.
¿Por qué?
Porque esta función combina:
Ese era el límite correcto para inspeccionar.
Y fue exactamente donde vivía el error.
El error se reduce a una pequeña cadena de confianza.
En el 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}))
la URL url controlada por el usuario se envía directamente a la llamada RPC.
Luego, en el 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))])
y:
(defn- create-file-media-object-from-url
[cfg {:keys [url name] :as params}]
(let [content (media/download-image cfg url)
el backend verifica que el llamante pueda editar el archivo de destino, luego pasa la URL controlada por el atacante a media/download-image.
La implementación de la búsqueda está aquí:
(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})
Y el cliente HTTP compartido está configurado como:
(http/build-client {:connect-timeout 30000
:follow-redirects :always}))
Esa es toda la vulnerabilidad:
Porque el atacante solo necesita:
La cadena de ataque es sencilla:
Ese es todo el error.
La distinción importante es dónde ocurre la solicitud.
La pregunta no es:
"¿Puede Penpot importar imágenes desde URL?"
La verdadera pregunta es:
"¿Puede un usuario autenticado hacer que el backend de Penpot se conecte a destinos internos a los que el usuario no debería poder acceder a través de la aplicación?"
En este caso, la respuesta fue sí.
Eso importa porque hay una diferencia real entre:
La validación de imágenes no elimina esa diferencia.
Reduce algunos casos de exfiltración directa, pero no elimina la condición SSRF ni la ruptura del límite de red.
Validé este problema con una prueba local controlada vinculada directamente a la ruta de código revisada de Penpot.
El objetivo no era alcanzar infraestructura de terceros. El objetivo era demostrar la propiedad de seguridad exacta: