
Preuve de concept démontrant une vulnérabilité de traversée de chemin (CVE-2026-35204) dans l'installation de plugins Helm, permettant l'écriture arbitraire de fichiers via des métadonnées de plugin malveillantes.
Ce dépôt représente une preuve de concept (PoC) pour la CVE-2026-35204
"Helm est un gestionnaire de paquets pour les Charts Kubernetes. De
4.0.0 to 4.1.3, un plugin Helm spécialement conçu, lors de son installation ou de sa mise à jour, amène Helm à écrire le contenu du plugin à un emplacement arbitraire du système de fichiers. Pour empêcher cela, validez que le plugin.yaml du plugin Helm ne contient pas de champ version: avec des séparateurs de chemin POSIX de type point-point, c'est-à-dire « /../ ». Cette vulnérabilité est corrigée dans4.1.4."
Ce PoC démontre que metadata.Version est utilisé pour construire le chemin de l'archive du plugin, permettant une traversée de chemin et l'écriture du plugin.tgz téléchargé en dehors du répertoire de plugin prévu. Le contenu du plugin est toujours extrait dans le répertoire de cache désigné avant d'être installé, donc ce PoC ne démontre ni extraction arbitraire ni écrasement arbitraire de fichier hôte.
Le programme d'installation de plugins Helm a une interface avec quatre implémentations (HTTP, local, VCS et OCI). Ce PoC se concentre sur le programme d'installation HTTP. Contrairement aux programmes d'installation HTTP/OCI, le programme d'installation VCS ne construit pas du tout un chemin de tarball à partir de metadata.Version. Voici un bloc de code qui prépare le tarballPath :
filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
tarballPath := helmpath.DataPath("plugins", filename)
où metadata.Version est la variable que nous contrôlons dans plugin.yaml. Pour déclencher la vulnérabilité, vous devez empaqueter votre plugin conçu dans une archive .tgz, car c'est son metadata.Version qui est extraite par plugin.ExtractTgzPluginMetadata et téléchargée via le programme d'installation HTTP/OCI/local. Par conséquent, pour réaliser une traversée de chemin, nous pouvons utiliser http_installer, qui récupère les plugins sous forme d'archive servie par un serveur web.
Dans l'exemple ci-dessous, le binaire helm a été reconstruit manuellement pour activer les messages de journalisation, où vous pouvez observer deux flux de données différents. First est l'installation d'un plugin avec un nom de version correct. Second est l'installation d'un plugin dont le nom de version contient des séparateurs de chemin de type point-point :
./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
Les traces d'exécution démontrent que la traversée de chemin n'affecte que l'emplacement où l'archive du plugin téléchargé est placée, ce qui est essentiellement la variable d'environnement HELM_PLUGINS. Le cache (i.CacheDir) et l'extraction finale (i.Path()) restent inchangés. Avec cela à l'esprit, nous pouvons manipuler la destination de l'archive temporaire et son nom, ce qui entraîne une écriture de fichier.
Vous n'avez pas besoin d'exécuter quoi que ce soit ni d'utiliser des webhooks post-installation, une simple installation de plugin suffit pour déclencher la vulnérabilité.
Lors de mon analyse du programme d'installation HTTP, j'ai observé que l'extraction de l'archive se produit dans i.CacheDir, tandis que l'installation (fs.CopyDir) copie les fichiers de i.CacheDir vers i.Path(). Je n'ai pas trouvé de chemin de code où le tarballPath influencé par la traversée affecte le contenu de l'archive. Par conséquent, sur la base du traçage d'exécution analysé, la destination d'extraction de l'archive est indépendante du tarballPath contrôlé par la traversée.
L'avis de sécurité mentionne à la fois l'installation et la mise à jour. Pour les programmes d'installation HTTP/local/VCS, Update() n'est pas implémenté ; le programme d'installation OCI implémente la mise à jour en appelant Install() après avoir supprimé le plugin précédent.
Voici la méthode Update() du programme d'installation HTTP :
func (i *HTTPInstaller) Update() error {
return fmt.Errorf("method Update() not implemented for HttpInstaller")
}
Pour atténuer la vulnérabilité, la fonction isValidSemver a été ajoutée, qui est désormais appelée lorsque Validate() dans metadata.go est invoqué.
"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))
}
Comme vous pouvez le voir, si la version n'est pas un semver valide (et ../../../../ n'en est pas un), nous recevrons une erreur lors du processus d'installation du plugin helm malveillant.
Pour exécuter le PoC, vous pouvez utiliser le script scripts/pos.sh :
./scripts/poc.sh
Ce qui donnera un résultat similaire à ceci :

MIT