LiteLLMが再び乗っ取り被害:悪意あるPythonパッケージが認証情報を窃取し、外部コードを実行

JFrog Security Research

LiteLLMが再び乗っ取り被害:悪意ある1.107.0のwheelが認証情報を窃取し、外部コードを読み込み

Guy Korolevski, JFrog Security Researcher | 2026年10月21日

 

【2026年10月22日更新】LiteLLMから、本インシデントは封じ込められたとの連絡がありました。同社は影響を受けた利用者向けの最新状況と対応指針を記載したインシデント更新情報を公開しています。

JFrog Security Researchチームは、複数の大規模言語モデル(LLM)プロバイダーへのアクセスを提供する広く利用されたPythonインターフェース兼プロキシ、litellm 1.107.0から悪意あるコードを発見しました。問題のwheelは正規プロジェクトと機能的には同等ですが、Python起動時に実行され、多様な認証情報と秘密情報を窃取し、1時間ごとに外部コードを実行できる経路を提供する3つのファイルが追加されていました。アプリケーションがlitellmをimportしなくてもペイロードは実行され得ます。

公式Dockerイメージ litellm/litellm:1.107.0 も影響を受けます。1.107.0を指していた期間に変更可能なlatest、main-latest、main-stableタグを取得した環境も、曝露したものとして扱う必要があります。Docker Hubからは影響を受けるイメージおよびタグが削除されています。

LiteLLMの乗っ取りが文書化されたのは2026年に入って2回目です。今回特に重要なのは、公開されたソース配布物には、wheel内の3つの悪意あるファイルが存在しない点です。ソースコード、Gitタグ、sdistだけを検査しても、プラットフォーム別の配布アーティファクトに潜む悪意ある挙動を見逃す可能性があります。オープンソースコミュニティはLiteLLM issue #47826で状況を追跡しており、PyPIは侵害された1.107.0リリースを削除しました。

1.107.0のPyPI wheelはmacOS、Linux、musl Linux、Windows向けの全7件にペイロードが含まれます。ソースtarballにはcook.pth、cook.py、sitecustomize.pyがありませんが、pyproject.tomlには対応するGitタグにはない data = “cook” 設定が追加されていました。JFrogがローカルで取得したsdistも、この設定を含む一方で3つの悪意あるファイルは含みません。

ペイロードのZIPタイムスタンプとメタデータは正規wheelファイルと一致しており、手作業で後からZIPを再パックしたのではなく、通常のwheel構築時に組み込まれたことを示唆します。ただし、誰がビルドツリーを変更したのか、どのように変更されたのか、どう公開されたのかは分かっていません。ビルドまたは公開工程の侵害の正確な原因は未解明です。

原文掲載画像:pypi-litellm.png
原文掲載画像(英語表記)

LiteLLMはすでに一度乗っ取られていた

2026年3月、JFrog Security ResearchはLiteLLMの最初の侵害を報告しました。パイプラインで固定されていない悪意あるTrivy 0.69.4を実行したことで、CI/CDおよび公開用の認証情報が漏えいしました。盗まれた認証情報はバックドア入りのlitellm 1.82.7と1.82.8の公開に使われ、これらは認証情報を収集し、Kubernetesノード間に拡散して永続化バックドアを設置しました。JFrogはこれらをXRAY-955589として追跡しています。

今回の1.107.0で使われたペイロードとインフラは異なります。現時点でTeamPCPや以前の侵入経路との関連を示す証拠はなく、別個の攻撃キャンペーンとして扱うべきです。

ソースアーカイブとビルド済みwheelが示す異なる実態

wheelのRECORDマニフェストには、litellm-1.107.0.data/purelib/配下に3つのファイルが含まれます。

litellm-1.107.0.data/purelib/cook.pth
litellm-1.107.0.data/purelib/cook.py
litellm-1.107.0.data/purelib/sitecustomize.py

data = “cook” はMaturinに対し、ビルド時のcook/ディレクトリをwheelのデータ領域へコピーするよう指示します。したがってwheelビルドには、公開されたソースアーカイブに存在しなかったファイルが利用可能だったことになります。

wheelインストーラーはpurelib内のファイルをPython環境のsite-packagesルートに配置します。対応する1.107.0のsdistにこれらのペイロードはありません。正規ライブラリのツリーとの比較では、コンパイル済みRustブリッジやパッケージ形式の違いといった通常の差異を除き、他に注入されたコードは確認されませんでした。

これはwheelまたはビルドアーティファクトの段階での注入です。観察結果はwheel構築中または構築後の注入と整合し、メタデータの一致は後からの手動再パックより通常のビルド工程への混入を支持します。ただし特定のCI、開発者端末、PyPIアカウント、公開認証情報が侵害されたと証明するものではありません。

運用面で重要なのは、ソースレビューだけでは正規のコードに見えても、実際にインストールするwheelに実行可能なファイルが追加されている可能性です。利用者とセキュリティツールは、sdistやリポジトリのタグを完全な表現とみなさず、RECORDおよび起動フックを含め、実際にインストールするwheelそのものを検証しなければなりません。

執筆時点でリモートローダーが利用するデッドドロップdomain.txtを読み取り専用で取得したところ、リダイレクトなしのHTTP 200と、ちょうど0バイトの空のプレーンテキスト本文が返りました。ドメインへの接続は発生せず、この観察時点でローダーは休止していました。ただし内容が変更されれば再稼働します。cook.pyによる認証情報の窃取はdomain.txtと独立して動作するため、wheel自体が安全になったわけではありません。

Pythonの起動フックが侵害を引き起こす

悪意あるwheelは2つのPython起動機構を利用します。1つ目のcook.pthに含まれるのは次の1行だけです。

import cook

Pythonのsiteモジュールは通常のインタープリター初期化時に.pthファイルを処理し、importで始まる行を実行します。このため、アプリケーションがlitellmをimportしなくても、影響を受ける環境でPythonが通常起動するとcook.pyが実行されます。

2つ目の仕組みはsitecustomize.pyです。Pythonはsiteパス処理後にsitecustomizeモジュールのimportを自動で試みます。注入されたモジュールはこの動作を利用して独立したローダーを開始します。両方のフックにより、pipの管理操作を含む通常のPython実行が再び悪意ある処理を起動させ得ます。Windowsではos.fork()に依存するsitecustomize.pyのデーモン経路は黙って失敗しますが、cook.pthは依然cook.pyをimportするため、認証情報窃取の危険は残ります。

cook.pyが認証情報窃取ツールprivcollectを起動

2行のcook.pyにはBase64エンコード、zlib圧縮されたPythonプログラムがあり、exec()へ直接渡されます。サンプルを実行せず静的に展開すると、およそ61KBのPythonソースが得られ、自らをprivcollectと名乗ります。以下は悪意あるエンコードデータの先頭だけを残した短縮例です。

import zlib as _3659944d, base64 as _35100b45
exec(_3659944d.decompress(_35100b45.b64decode('eNrdfWt327qu4Pf+Ck/X...')))

cook.pyはデーモンスレッドで収集処理を開始し、/tmp/.pc_once_72129118c59f5636c0bbを1回限り実行の目印にします。この目印は情報の外部送信が成功した後にだけ作られるため、ファイルがないことは未侵害の証拠ではありません。重複実行の抑制には環境変数PRIVCOLLECT_ACTIVE=1も使用します。

窃取対象

  • SSHファイル、GPG秘密鍵、TLS秘密鍵、シェル・データベースクライアント履歴、.pem/.key/.p12/.pfxなどの鍵ファイル
  • Chromium系ブラウザーのパスワード、Cookie、Webデータ、履歴、Firefoxのログイン・鍵データベースと履歴
  • AWS、Azure、Google Cloudの認証情報、Kubernetes設定・サービスアカウントトークン、Docker/Swarmシークレット、GitHub CLI、rclone、.netrc
  • GitLab Runner、Jenkins、Vault、Terraform、PackerのCI/CD・Infrastructure as Code関連シークレット
  • Git認証情報、.npmrc、.pypirc、pip、Yarn、Maven、Gradle、Composer、RubyGems、Cargoのパッケージ管理・ソース管理認証情報
  • Cursor、Claude、Codex、Gemini、GitHub Copilot、Hugging Face、Kiro、Windsurf、Zed、Continue、aider、goose、opencode、Qwen、Cline、Roo、CodyのAI開発認証情報・設定
  • 現在のプロセスの環境変数と、Linuxで読み取り可能なすべての/proc/<pid>/environ
  • AWS IMDS/ECSタスク、Google Cloud、Azure IMDSのクラウドインスタンス認証情報
  • DATABASE_URL経由でアクセスするLiteLLMプロキシデータ(LiteLLM_CredentialsTable、LiteLLM_ProxyModelTable)

以下の短縮コード画像には、デコードされた収集モジュールの一部が示されています。

原文掲載画像:credential-modules.png
原文掲載画像(英語表記)

Linuxでは他プロセスの環境変数も読み取ります。

原文掲載画像:process-environment.png
原文掲載画像(英語表記)

LiteLLM固有のデータベースモジュールは特に重大です。子Pythonプロセスを起動し、Prisma経由で接続して、LiteLLM_CredentialsTableに保存されたLLMプロバイダーの認証情報とLiteLLM_ProxyModelTableのプロキシモデル設定を取得しようとします。プロキシホスト1台の侵害が、複数の上流AIプロバイダーの認証情報漏えいにつながり得ます。

原文掲載画像:database-collection.png
原文掲載画像(英語表記)

クラウドモジュールはGetCallerIdentityによって盗んだAWS認証情報の有効性も確認しようとします。防御側はローカルでの窃取だけでなく、外部へ持ち出されたクラウド認証情報の後続利用にも注意する必要があります。

LiteLLMの公式サイトに似せたドメインへの暗号化された情報送信

収集したデータはJSON化され、ランダムなAES-256-CBC鍵で暗号化されます。その鍵は埋め込みのRSA-4096公開鍵とOAEP-SHA256で包まれ、メタデータ用hdr.json、IVと暗号データ用data.bin、暗号化されたAES鍵用key.binを含むtar.gzに格納されます。

原文掲載画像:c2-exfiltration.png
原文掲載画像(英語表記)

アーカイブは平文HTTPで hxxp[:]//www[.]litellm[.]dev/healthcheck に送信されます。リクエストにはX-Tokenヘッダーの固定トークン XWiA8gZNa7NcX2XRoAEb とX-SHA256ヘッダーが含まれます。転送自体は暗号化されていませんが、データはアプリケーション層で暗号化されるため、攻撃者のRSA秘密鍵がなければネットワークキャプチャから窃取内容を復元できません。

登録記録によるとlitellm[.]devは2026年10月13日に作成され、10月18日に更新されました。公式プロジェクトはlitellm[.]aiを使用しており、litellm[.]devは似せたドメインです。今回のwww[.]litellm[.]devと前回のmodels[.]litellm[.]cloudは混同してはいけません。

sitecustomize.pyが1時間ごとのリモートコードローダーを作成

注入されたsitecustomize.pyは/tmp/remote_code_loader.lockを開いて非ブロッキングの排他flockを取得します。既存インスタンスが保持していなければos.fork()を呼び出し、親は起動処理へ戻り、子はos.setsid()を実行して標準入出力を/dev/nullへ向け、作業ディレクトリを/へ変更し、無限の1時間周期ループに入ります。

各サイクルでは次のURLから候補C2ドメインを取得します。

hxxps[:]//raw[.]githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt

最初の有効なドメインが名前解決できれば、2つ目の埋め込みprivcollectを同期実行し、hxxp[:]//{domain}/codepy からPythonコードを取得してcompile()とexec()で実行します。コードの完全性検証や認証がないため、接続先サーバーの管理者は影響を受ける利用者権限で任意コードを実行できます。

原文掲載画像:remote-code-loader.png
原文掲載画像(英語表記)

こちらの窃取ツールはPC_ONCE_PATHを空に、PC_ASYNCをFalseに設定します。したがって毎回のC2サイクルで同期的に収集と外部送信を実行します。一方、cook.py側は送信成功後、ホストにつき1回の完了を意図しています。執筆時点でdomain.txtが0バイトだったという観察は、この条件付きの1時間周期経路にしか影響しません。cook.pthによる情報窃取は引き続き実行可能で、デッドドロップが変更されればローダーは再稼働できます。

対処方法

  • litellm 1.107.0のwheelはすべて避けてください。7つのプラットフォーム向けwheelはすべて影響を受けます。litellm==1.106.2への固定は、起動フックの除去後または新規に作成したクリーンな環境からのみ行ってください。
  • 問題のwheelがインストールされた、またはその環境でPythonが起動されたホスト/コンテナを隔離してください。アクセス可能だった認証情報とデータは侵害されたものとして扱います。
  • 影響を受けた環境でpipを実行することから復旧を始めないでください。Python起動で悪意あるフックが再実行されます。まず該当Pythonを呼び出さないツールでcook.pth、cook.py、sitecustomize.py、__pycache__配下の対応するコンパイル済みファイル、/tmp/.pc_once_72129118c59f5636c0bb、/tmp/remote_code_loader.lockを削除してください。
  • sitecustomize.py関連の切り離されたPythonプロセスを調査・終了してください。特にロックを保持する、setsidを使用する、作業ディレクトリが/のプロセスを確認し、/tmp/.pc_sq_*、/tmp/.pc_e_*も調査してください。
  • www[.]litellm[.]dev、デッドドロップURL、およびそこから返されるドメインへのアクセスを遮断・調査し、プロキシ、DNS、エンドポイント、クラウドの監査ログを保全してください。
  • 影響ホストからアクセスできた認証情報をすべてローテーションしてください。ブラウザー、SSH/GPG/TLS鍵、パッケージ公開/ソース管理トークン、クラウド/Kubernetes、AI開発、DATABASE_URL、LiteLLMマスターキー・仮想キー、LiteLLM_CredentialsTableに保存された全プロバイダー認証情報などを含みます。
  • /codepyの取得・実行痕跡を調査してください。この段階では任意Pythonコードを実行でき、影響は内蔵窃取ツールの動作だけに限定できません。
  • 確認済みアーティファクトを使い、影響を受けた仮想環境・ホスト・コンテナ・イメージをフォレンジック観点から再構築してください。全ての起動アーティファクト、追加段階のコード、漏えいしたシークレットを漏れなく除去できたと証明するより安全です。

結論

悪意あるlitellm 1.107.0は、ソースの来歴と配布アーティファクトの完全性の間にある重大なギャップを示しました。ソース配布物には実行可能なペイロードがなく、wheelはそれをsite-packagesへ直接インストールし、通常のアプリケーションコードより先に起動させます。sdistに追加されたビルド設定と一致するアーティファクトメタデータは調査上の手掛かりですが、正確な根本原因を確定するものではありません。

LiteLLMの2回目の乗っ取りを、以前のTeamPCP事案と混同すべきではありません。双方ともAIパッケージへの信頼を悪用して開発者・クラウド認証情報を広範に狙いますが、今回のアーティファクトは起動ファイル、永続化の挙動、ネットワークインフラが異なります。インシデント対応ではそれぞれ固有の侵害指標を使う必要があります。JFrog XrayとJFrog CurationはXRAY-955589を使用して悪意あるlitellm 1.107.0を検出します。

侵害の痕跡(IoC)

  • 悪意あるlitellm 1.107.0(PyPI):XRAY-955589
  • 影響を受けるDockerイメージのdigest:sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e
  • 情報送信URL:hxxp[:]//www[.]litellm[.]dev/healthcheck
  • 情報送信ドメイン:www[.]litellm[.]dev
  • C2デッドドロップURL:hxxps[:]//raw[.]githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt
  • 条件付きリモートペイロードURL:hxxp[:]//{domain}/codepy
  • デッドドロップリポジトリ:letters9813/pythonclient-test
  • 情報送信トークン:X-Token: XWiA8gZNa7NcX2XRoAEb
  • 単一インスタンス用ロック:/tmp/remote_code_loader.lock
  • 1回限り実行マーカー:/tmp/.pc_once_72129118c59f5636c0bb
  • 一時ファイル:/tmp/.pc_sq_* および /tmp/.pc_e_*
  • LiteLLMデータベース対象:LiteLLM_CredentialsTable、LiteLLM_ProxyModelTable
  • コミュニティでの追跡:LiteLLM security issue #47826

原文:https://research.jfrog.com/post/litellm-hijacked-again/