
Proof-of-concept che dimostra una vulnerabilità di path traversal (CVE-2026-35204) nell'installazione del plugin Helm, consentendo la scrittura arbitraria di file tramite metadati di plugin appositamente creati.
Questo repository rappresenta un PoC per una CVE-2026-35204
"Helm è un package manager per Chart per Kubernetes. Da 4.0.0 a 4.1.3, un plugin Helm appositamente creato, quando installato o aggiornato, causerà la scrittura da parte di Helm del contenuto del plugin in una posizione arbitraria del filesystem. Per prevenire ciò, verificare che il plugin.yaml del plugin Helm non includa un campo version: contenente separatori di percorso POSIX dot-dot, cioè "/../". Questa vulnerabilità è stata corretta in 4.1.4."
Questo PoC dimostra che metadata.Version viene utilizzato per costruire il percorso dell'archivio del plugin, consentendo il path traversal e la scrittura del plugin.tgz scaricato al di fuori della directory del plugin prevista. Il contenuto del plugin viene comunque estratto nella directory cache designata prima di essere installato, quindi questo PoC non dimostra un'estrazione arbitraria o una sovrascrittura arbitraria di file dell'host.
La vulnerabilità è classificata come CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
L'installer dei plugin Helm ha un'interfaccia con quattro implementazioni (HTTP, locale, VCS e OCI). Questo PoC si concentra sull'installer HTTP. A differenza degli installer HTTP/OCI, l'installer VCS non costruisce affatto un percorso tarball da metadata.Version. Ecco un blocco di codice che prepara il tarballPath:
filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
tarballPath := helmpath.DataPath("plugins", filename)
dove metadata.Version è la variabile che controlliamo in plugin.yaml. Per attivare la vulnerabilità, è necessario impacchettare il plugin creato in un archivio .tgz, poiché il suo metadata.Version è quello estratto da plugin.ExtractTgzPluginMetadata e scaricato tramite l'installer HTTP/OCI/locale. Pertanto, per ottenere il path traversal, possiamo usare http_installer, che recupera i plugin come archivio servito da un web server.
Nell'esempio seguente, il binario helm è stato ricompilato manualmente per abilitare i messaggi di log, dove è possibile osservare due diversi flussi di dati. Il primo è l'installazione del plugin con il nome di versione corretto. Il secondo è l'installazione del plugin che ha separatori di percorso dot-dot nel suo nome di versione:
./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
Le tracce di runtime dimostrano che il path traversal influisce solo sulla posizione in cui viene collocato l'archivio del plugin scaricato, che è essenzialmente la variabile d'ambiente HELM_PLUGINS. La cache (i.CacheDir) e l'estrazione finale (i.Path()) rimangono invariate. Tenendo presente ciò, possiamo manipolare la destinazione dell'archivio temporaneo e il suo nome, ottenendo una scrittura di file.
Non è necessario eseguire nulla o utilizzare webhook post-installazione, la semplice installazione del plugin è sufficiente per attivare la vulnerabilità.
Durante la mia analisi dell'installer HTTP, ho osservato che l'estrazione dell'archivio avviene in i.CacheDir, mentre l'installazione (fs.CopyDir) copia i file da i.CacheDir in i.Path(). Non ho trovato un percorso di codice in cui il tarballPath influenzato dal traversal influenzi il contenuto dell'archivio. Pertanto, in base alla traccia di runtime analizzata, la destinazione di estrazione dell'archivio è indipendente dal tarballPath controllato dal traversal.
L'advisory menziona sia l'installazione che l'aggiornamento. Per gli installer HTTP/locale/VCS, Update() non è implementato; l'installer OCI implementa l'aggiornamento invocando Install() dopo aver rimosso il plugin precedente.
Ecco il metodo Update() dell'installer HTTP:
func (i *HTTPInstaller) Update() error {
return fmt.Errorf("method Update() not implemented for HttpInstaller")
}
Per mitigare la vulnerabilità, è stata aggiunta la funzione isValidSemver, che ora viene chiamata quando viene invocato Validate() all'interno di 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))
}
Come si può vedere, se la versione non è un semver valido (e ../../../../ non lo è), riceveremo un errore durante il processo di installazione del plugin helm malevolo.
Per eseguire il PoC è possibile utilizzare lo script scripts/pos.sh:
./scripts/poc.sh
Che produrrà qualcosa di simile a questo:

MIT