
Prova de conceito demonstrando uma vulnerabilidade de travessia de caminho (CVE-2026-35204) na instalação de plugins do Helm, permitindo escrita arbitrária de arquivos por meio de metadados de plugin manipulados.
Este repositório representa um PoC para o CVE-2026-35204
"O Helm é um gerenciador de pacotes para Charts do Kubernetes. Das versões 4.0.0 a 4.1.3, um plugin Helm especialmente criado, quando instalado ou atualizado, fará o Helm escrever o conteúdo do plugin em um local arbitrário do sistema de arquivos. Para evitar isso, valide se o plugin.yaml do plugin Helm não inclui um campo version: contendo separadores de caminho POSIX dot-dot, ou seja, "/../". Esta vulnerabilidade é corrigida na versão 4.1.4."
Este PoC demonstra que metadata.Version é usado para construir o caminho do arquivo do plugin, permitindo path traversal e a escrita do plugin.tgz baixado fora do diretório de plugins pretendido. O conteúdo do plugin ainda é extraído para o diretório de cache designado antes de ser instalado, portanto este PoC não demonstra extração arbitrária ou sobrescrita arbitrária de arquivos do host.
O instalador de plugins do Helm possui uma interface com quatro implementações (HTTP, local, VCS e OCI). Este PoC concentra-se no instalador HTTP. Ao contrário dos instaladores HTTP/OCI, o instalador VCS não constrói um caminho de tarball a partir de metadata.Version de forma alguma. Aqui está um bloco de código que prepara o tarballPath:
filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
tarballPath := helmpath.DataPath("plugins", filename)
onde metadata.Version é a variável que controlamos no plugin.yaml. Para acionar a vulnerabilidade, você precisa empacotar seu plugin criado em um arquivo .tgz, pois é o metadata.Version dele que é extraído por plugin.ExtractTgzPluginMetadata e baixado via instalador HTTP/OCI/local. Portanto, para alcançar path traversal, podemos usar o http_installer, que busca plugins como um arquivo servido por um servidor web.
No exemplo abaixo, o binário do helm foi recompilado manualmente para habilitar mensagens de log, onde você pode observar dois fluxos de dados diferentes. Primeiro é a instalação do plugin com um nome de versão correto. Segundo é a instalação do plugin que possui separadores de caminho dot-dot no nome da versão:
./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
Os rastros de execução demonstram que o path traversal afeta apenas o local onde o arquivo do plugin baixado é colocado, que é basicamente a variável de ambiente HELM_PLUGINS. O cache (i.CacheDir) e a extração final (i.Path()) permanecem inalterados. Com isso em mente, podemos manipular o destino e o nome do arquivo temporário, resultando em uma escrita de arquivo.
Você não precisa executar nada ou usar webhooks pós-instalação; a simples instalação do plugin é suficiente para acionar a vulnerabilidade.
Durante minha análise do instalador HTTP, observei que a extração do arquivo ocorre em i.CacheDir, enquanto a instalação (fs.CopyDir) copia os arquivos de i.CacheDir para i.Path(). Não encontrei um caminho de código onde o tarballPath influenciado pelo traversal afete o conteúdo do arquivo. Portanto, com base no rastreamento de execução analisado, o destino da extração do arquivo é independente do tarballPath controlado pelo traversal.
O aviso menciona tanto instalação quanto atualização. Para os instaladores HTTP/local/VCS, Update() não é implementado; o instalador OCI implementa a atualização invocando Install() após remover o plugin anterior.
Aqui está o método Update() do instalador HTTP:
func (i *HTTPInstaller) Update() error {
return fmt.Errorf("method Update() not implemented for HttpInstaller")
}
Para mitigar a vulnerabilidade, a função isValidSemver foi adicionada, que agora é chamada quando Validate() dentro de metadata.go é invocado.
"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 você pode ver, se a versão não for um semver válido (e ../../../../ não é), receberemos um erro no processo de instalação do plugin helm malicioso.
Para executar o PoC, você pode usar o script scripts/pos.sh:
./scripts/poc.sh
Que resultará em algo como isto:

MIT