LiteLLM 1.107.0 のマルウェア混入|影響確認と初動対応

  • URLをコピーしました!
目次

はじめに

2026 年 10 月 21 日、LLM の呼び出しを統一する Python ライブラリ兼 AI Gateway の LiteLLM で、認証情報の窃取とリモートコード実行の機能を持つマルウェアを含むバージョン 1.107.0 が、PyPI と公式コンテナレジストリに公開されました。公開から除去までは約 2 時間ですが、latest などの可変タグも対象に含まれたため、バージョンを固定していない Python 環境や、イメージを自動で再取得するコンテナ環境が影響を受けた可能性があります。

LiteLLM は 10 月 21 日にインシデント報告を、10 月 23 日に攻撃経路の確定調査を公開しました。初報に含まれていた推測の一部は、確定調査で訂正されています。本記事では両資料を照合し、運用担当者が自社環境の影響を判定し、初動対応を進めるための確認ポイントを整理します。本記事の確認基準日は 2026 年 10 月 26 日です。内容は公開情報に基づく整理であり、感染環境での実機検証は行っていません。

この記事でわかること
  • LiteLLM 1.107.0 の影響を受けた PyPI・Docker Hub・GHCR のアーティファクトと時間帯(JST 併記)
  • Agent Platform のマスターキー漏洩から悪性リリース公開に至った攻撃経路と、初報からの訂正点
  • 感染が疑われる環境で Python を起動せずに痕跡を確認する方法
  • 証拠保全、秘密情報のローテーション、クリーンな再構築までの初動手順
  • 3 月の LiteLLM 1.82.7 / 1.82.8 事案との違い

結論として、影響時間帯(JST で 10 月 21 日 20:39〜23:20 の範囲)に litellm==1.107.0 をインストールした環境や、対象タグのイメージを取得・起動した環境は、侵害を前提に調査と秘密情報のローテーションを進める必要があります。判定はタグ名ではなく、当時取得・実行したバージョンと image digest で行います。感染が疑われる環境では pip show を含めて Python を起動せず、シェルや別の調査環境からファイルと記録を確認してください。

LiteLLM 1.107.0 の影響範囲と対象時間

公式インシデント報告で影響を受けたとされるアーティファクトは次のとおりです。時刻は公式資料の UTC と、日本時間(JST、UTC+9)を併記しています。

配布先対象公開開始除去・利用不可侵害された image index digest
PyPIlitellm==1.107.0(公開された 7 個の wheel すべて)11:39:51 UTC(20:39:51 JST)13:23 UTC までに隔離(22:23 JST)該当なし
Docker Hubタグ v1.107.0latestmain-latestmain-stable11:46:56 UTC(20:46:56 JST)14:20 UTC(23:20 JST)に除去、または検証済みイメージへ付け替えsha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e
GHCR ghcr.io/berriai/litellm上記と同じ 4 タグ11:46:56 UTC(20:46:56 JST)13:51 UTC までに利用不可(22:51 JST)sha256:1bbcb1443bc9…(公式は短縮表示)
GHCR ghcr.io/berriai/litellm-database上記と同じ 4 タグ11:46:56 UTC(20:46:56 JST)13:51 UTC までに利用不可(22:51 JST)sha256:c00f9d92c427…(公式は短縮表示)
GHCR ghcr.io/berriai/litellm-non_root上記と同じ 4 タグ11:46:56 UTC(20:46:56 JST)13:51 UTC までに利用不可(22:51 JST)sha256:7b00e91cd2fe…(公式は短縮表示)

GHCR の 3 系列について、公式資料は digest を先頭 12 桁の短縮表示で掲載しています。短縮値は調査候補の絞り込みに使用し、前方一致をもって同一のアーティファクトと判定しないでください。ghcr.io/berriai/litellm の短縮値は Docker Hub の digest と先頭が一致し、第三者分析は両者を同じ image index と報告していますが、GHCR 側の全桁の値は公式資料で確認できません。全桁の digest を確認できない場合は、短縮値の一致を「影響の可能性が高い候補」として扱い、取得時刻、タグ、実行履歴と組み合わせて判断したうえで、digest による確定ができていないことを調査記録に残してください。照合の前には、公式資料の最新版で全桁の値が追記されていないかも確認してください。

判定はタグではなく当時の digest で行う

Docker Hub の latest などのタグは、14:20 UTC 以降は検証済みのイメージへ付け替えられています。現在の latest が安全であることは、影響時間帯に取得したイメージの安全性を示しません。ノードやレジストリのキャッシュに当時のイメージが残っている場合、コンテナの再起動時にそのイメージが使われる可能性もあります。

照合では、マルチアーキテクチャの image index digest と、amd64 / arm64 などプラットフォーム別の manifest digest を区別してください。公式資料が示しているのは image index digest です。実行環境の記録にプラットフォーム別の digest しか残っていない場合は、公式資料や第三者分析と照合し、一致を確認できない値を推測で判定しないことを推奨します。

「取得」「インストール」「実行」「流出」を区別する

影響の判定では、悪性アーティファクトがどの段階まで進んだかを分けて考えます。

取得

wheel やイメージをダウンロードした段階です。パッケージキャッシュ、社内ミラー、レジストリのキャッシュに悪性アーティファクトが残り、後から再利用される可能性があります。

インストール

wheel が site-packages に展開された段階です。cook.pth と sitecustomize.py が配置され、以降の Python 起動で悪性コードが動作する状態になります。公式は、影響時間帯に pip install を実行した環境を侵害されたものとして扱うよう案内しています。

実行

Python インタープリターの起動、または影響イメージからのコンテナ起動です。import litellm を行わなくても、その環境で Python が起動すれば悪性コードが実行されます。

外部通信による情報流出

公式は、外向き通信が可能なホストに 1.107.0 がインストールされていた場合、情報の持ち出しは成功したと想定するよう求めています。

Enterprise 版と現在の更新先

公式によると、Enterprise 顧客向けの private registry から配布されたリリースは影響を受けていません。ただし、Enterprise 契約がある組織でも、検証環境や開発者の PC で PyPI のパッケージや Docker Hub のイメージを使っていた可能性があります。契約の有無ではなく、実際にどのレジストリから、どのアーティファクトを取得したかで判断してください。

10 月 21 日の公式インシデント報告は、当面の対応として 1.106.2 など、1.107.0 より前の明示的なバージョンへ固定するよう案内していました。その後、確認基準日までに LiteLLM 公式から次のリリースが公開されています。

バージョン公開日(PyPI)主な内容(公式リリース情報による)
1.106.210 月 21 日10 月 21 日の公式インシデント報告が固定先として例示したバージョン
1.106.310 月 24 日eMudhra 系のルート CA を信頼ストアから除去。コンテナイメージなどの署名鍵を変更
1.106.410 月 25 日リモートのモデル料金表に接続できない場合に起動時エラーで終了する不具合の修正、イメージスキャンでの指摘への対応

これらは LiteLLM 公式が公開したリリースであり、本記事で各バージョンの安全性を独自に検証したものではありません。確定調査は、安全性を確認できた場合にのみリリースするとしていますが、侵害後のリリース手順の見直し内容は、翌週に共有するとして確認基準日時点では公開されていません。更新する際は、GitHub Releases と公式ブログで最新の案内を確認し、ロックファイルとイメージ digest で固定してください。

CI/CD でイメージの署名を検証している場合は、署名鍵の変更に注意が必要です。1.106.4 のリリースノートは、コミット e82a3c6 の cosign.pub を使う検証手順を示しています。従来の鍵(コミット 0112e53)を固定した CI やアドミッションポリシーでは新しいリリースの検証に失敗するため、対象バージョンに応じて使用する鍵を整理してください。なお、署名の検証で確認できるのは署名した鍵であり、前述のとおりビルド元のソースの正しさまでは示しません。

cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/e82a3c646908ba0a6ee0beddc4310d7a6cac4065/cosign.pub \
  ghcr.io/berriai/litellm:v1.106.4

マスターキー漏洩から悪性リリース公開までの攻撃経路

10 月 23 日の確定調査によると、起点は GitHub のトークンではなく、公開リポジトリへ誤ってコミットされた LiteLLM Agent Platform のマスターキーでした。コミットは上書きされたものの、キーが失効されなかったことが、約 5 か月後の侵害につながっています。

参考: The attack chain behind the v1.107.0 LiteLLM release(LiteLLM 公式)
“key revocation did not occur for the master key.”
(マスターキーの失効は行われませんでした。)
https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain

日時(UTC)日時(JST)出来事
5 月 27 日 18:535 月 28 日 03:53Agent Platform のマスターキーを含む設定ファイルが、公開リポジトリ LiteLLM-Labs/lite-harness にコミットされる。コミットは force push で上書きされたが、マスターキーは失効されなかった
10 月 10 日 21:13〜21:2810 月 11 日 06:13〜06:28攻撃者がマスターキーで Agent Platform API にアクセスし、管理対象エージェントの設定から GitHub 組織 owner の GitHub CLI OAuth token を取得
10 月 10 日 22:11〜22:1410 月 11 日 07:11〜07:14owner のトークンで一時ブランチを作成し、CI ワークフローから GH_TOKEN(2 人目のメンテナーのトークン)や RELEASE_PLEASE_TOKEN(3 人目のメンテナーのトークン)を外部へ送信。実行記録は削除
10 月 12 日 12:08〜12:3010 月 12 日 21:08〜21:30複数の private repository で同様の手口により、追加の秘密情報を窃取
10 月 21 日 10:08:29〜10:08:3410 月 21 日 19:08:29〜19:08:34リリース用の private repository BerriAI/project-releaser への変更が branch protection に拒否された 5 秒後、owner のトークンで保護を無効化して push
10 月 21 日 10:46〜11:1110 月 21 日 19:46〜20:11参照元を Berri-AI/litellm に変更した正規のリリースワークフローが、staging と production を実行。production の承認ゲートは 2 人目のトークンで承認
10 月 21 日 11:39〜11:4610 月 21 日 20:39〜20:46PyPI に 7 個の wheel、Docker Hub と GHCR にイメージが公開
10 月 21 日 11:53〜12:2410 月 21 日 20:53〜21:243 人目のトークンでワークフローの実行記録を削除し、owner のトークンで force push して変更履歴を隠蔽

初報からの訂正点

10 月 21 日の初報は、インターネットに公開されていた内部の LiteLLM デプロイメントのログから GitHub CLI OAuth token が漏れた可能性を示していました。10 月 23 日の確定調査は、リクエストログをもとに、最初のトークンが別サービスである Agent Platform の API 経由で取得されたと特定し、初報の推測を訂正しています。初報のページには調査途中の記述が記録として残されているため、攻撃経路は確定調査を参照してください。

BerriAI/litellm と Berri-AI/litellm の違い

LiteLLM の正規のソースリポジトリは BerriAI/litellm です。攻撃者は、ハイフンを含む類似名の組織 Berri-AI に fork を用意し、悪性ファイルを追加したコミットをリリースの参照元に指定しました。確定調査によると、fork 上のコミットは GitHub の仕様により元リポジトリの URL でも表示されるため、URL だけでは正規のコミットと区別しにくい状態でした。

GitHub 上の正規の v1.107.0 ソースタグには、悪性ファイルは含まれていません。そのため、ソースコードの監査だけでは今回の侵害を検出できず、配布されたアーティファクト側の確認が必要になります。第三者の StepSecurity の分析は、悪性ファイルが wheel とイメージにのみ含まれていたことや、イメージのビルド来歴(provenance)にソースとして Berri-AI/litellm が記録されていたことを報告しています。

署名があってもビルド元の安全性は保証されない

確定調査によると、攻撃者の変更は branch protection に一度拒否されましたが、組織 owner の権限で保護が無効化されました。その後、正規のリリースワークフローが fork を参照してビルドを行い、パイプラインが v1.107.0 に署名して公開しています。承認には盗まれた別のメンテナーのトークンが使われました。通常は事務所内の物理端末に紐づく特別なアカウントが承認する運用でしたが、同じ週にバックポート対応のため一時的に緩和されていたと説明されています。

署名や正規パイプラインの来歴は「どのパイプラインで作られたか」を示すものであり、ビルド元のソースが正しいことまでは保証しません。成果物を取り込む側では、署名の有無に加えて、来歴に記録されたソースリポジトリとコミットが想定どおりかを確認することを推奨します。なお、公式資料は wheel ごとの署名の詳細までは説明していないため、本記事ではその点を断定していません。

3 月の LiteLLM 侵害(1.82.7 / 1.82.8)との違い

LiteLLM は 2026 年 3 月にも、PyPI 経由のマルウェア混入を受けています。侵害の起点、配布経路、痕跡が異なるため、3 月の IoC や削除手順を今回の事案へそのまま流用しないでください。

観点2026 年 3 月2026 年 10 月
対象litellm 1.82.7 / 1.82.8litellm 1.107.0 と公式 Docker イメージ
侵害の起点Trivy の侵害により、CI から PyPI 公開用トークンが窃取された公開リポジトリに残った Agent Platform のマスターキーから、GitHub owner のトークンが取得された
公開経路窃取したトークンで PyPI へ直接アップロード正規のリリースワークフローが攻撃者の fork からビルド・署名・公開
配布先PyPIPyPI、Docker Hub、GHCR
主な実行ファイルlitellm_init.pth(1.82.8)、proxy_server.py への埋め込み(1.82.7)cook.pth、cook.py、sitecustomize.py
公式が案内した固定先(案内時点)1.82.6 以前、または再リリース後の 1.83.0 以降(3 月時点の案内であり、その後の 1.107.0 は対象外)1.106.2 など 1.107.0 より前の明示的なバージョン(10 月 21 日時点の案内)。その後 1.106.3 と 1.106.4 が公開

3 月の事案の挙動と当時の検出手順は『litellm 1.82.7 / 1.82.8 のマルウェア混入と検出・修復の手順』で解説しています。Trivy 侵害を起点とした攻撃の連鎖は、Trivy 関連サプライチェーン攻撃の解説記事を参照してください。

自社環境が影響を受けたか確認する方法

確認は「アーティファクトを取得したか」「インストール・実行したか」「外部へ通信したか」の順に、記録とファイルの両面から進めます。感染が疑われる環境では Python を起動しないことが前提です。

感染が疑われる環境では pip show も実行しない

公式インシデント報告は、確認方法の一つとして pip show litellm を挙げています。一方で同じ報告は、影響環境で Python を起動すると cook.pth と sitecustomize.py が再実行され、pip の実行もこれに含まれると警告しています。pip show も Python を起動するため、感染が疑われる環境では実行せず、ファイルシステム上の dist-info ディレクトリで確認します。

編集上の推奨として、調査は次の方針で進めます。

  • 停止できる環境は、スナップショットやディスクイメージを取得し、信頼できる別の調査環境から読み取り専用でマウントして確認する
  • コンテナイメージは、コンテナを起動しない docker create と docker export で中身を確認する
  • 稼働中のホストを確認する場合は、find や ls などのシェルコマンドだけを使う
  • 本番コンテナ内で python、pip、pip uninstall、pip install を実行しない

確認対象となる痕跡と判断の注意点

公式資料と第三者分析で示された主な確認対象は次のとおりです。公式は、litellm-1.107.0.dist-info、cook.pth、sitecustomize.py のいずれかが見つかれば露出を確認できると説明していますが、一般的な Python 環境にも存在し得るファイル名は、場所と内容をあわせて判断します。

確認対象意味判断の注意点
litellm-1.107.0.dist-info1.107.0 がインストールされたことを示すメタデータディレクトリ存在すれば、インストールの事実を確認できる
cook.pthsite-packages 直下に置かれ、Python 起動時に cook を読み込むLiteLLM の正規リリースには含まれないファイル
cook.py認証情報を収集する本体(第三者分析による)cook.pth と同じ site-packages 直下にあるかを確認する
sitecustomize.pyPython 起動時に自動で読み込まれ、常駐プロセスを起動する(第三者分析による)Debian / Ubuntu 系のシステム Python などで正規に存在する場合がある。site-packages 直下で cook.pth と同居しているか、内容と更新日時を照合する
__pycache__/cook*.pyc、__pycache__/sitecustomize*.pyc実行時に生成されたバイトコード実行の手がかりになる。公式は削除対象に含めている
/tmp/remote_code_loader.lock公式によると、切り離された Python デーモンが保持するロックファイル保持しているプロセスがあればデーモンが稼働中。コンテナの再作成や /tmp の掃除で消える
image digestコンテナが実行したイメージの識別子タグ名ではなく digest で照合する

参考: Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised(LiteLLM 公式)
“not finding one does not prove the environment is clean.”
(見つからないことは、その環境がクリーンであることを証明しません。)
https://docs.litellm.ai/blog/security-incident-oct-21-2026

コンテナは再作成されると当時のファイルが残らず、/tmp のファイルも短期間で消えます。痕跡ファイルが見つからない場合でも、次に説明する実行履歴と通信記録で判断してください。

リポジトリと構成ファイルで影響の可能性を洗い出す

最初に、1.107.0 を解決し得る依存指定や、可変タグを参照する構成を洗い出します。対象はソースリポジトリのため、調査用の端末で実行します。

# requirements や pyproject.toml で 1.107.0 を指定していないか
grep -rnE 'litellm[^0-9]{0,6}1\.107\.0' --include='requirements*.txt' --include='pyproject.toml' --include='Pipfile*' .

# uv.lock / poetry.lock で 1.107.0 に解決していないか(name と version が別行のため)
grep -rn -A1 --include='*.lock' 'name = "litellm"' . | grep '1\.107\.0'

# Dockerfile、Compose、Helm values などで対象タグやタグ省略の参照がないか
grep -rnE '(berriai/litellm[a-z_-]*|litellm/litellm)(:(v1\.107\.0|latest|main-latest|main-stable))?([[:space:]"]|$)' .

litellm>=1.x のような下限だけの指定や、タグを省略したイメージ参照(暗黙の latest)は、影響時間帯にビルドや再取得をしていれば対象になり得ます。CI のジョブ履歴と突き合わせて判断してください。

コンテナイメージと実行履歴を digest で確認する

Docker ホストでは、Docker CLI でローカルのイメージとコンテナの記録を確認します。次のコマンドはコンテナを起動しません。<image-id> は、1 つ目のコマンドで確認したローカルのイメージ ID に置き換えます。

# ローカルに残る LiteLLM イメージの ID と digest
docker image ls --digests --no-trunc | grep -iE 'berriai/litellm|litellm/litellm'

# イメージの ID と RepoDigests
docker image inspect --format '[1].Id [2]json .RepoDigests' <image-id>

# 停止済みを含め、そのイメージから作成されたコンテナと作成時刻
docker ps -a --no-trunc --filter 'ancestor=<image-id>' --format '[3].ID [4].CreatedAt [5].Status'

中身を調べる前に、対象のイメージとメタデータを保全します。出力先はホスト外の保全用ストレージとし、ハッシュ値を記録しておくと、後の解析で同一性を確認できます。停止済みの調査対象コンテナがある場合は、その設定とファイルシステムもあわせて保全します。docker export にはボリュームの内容が含まれないため、ボリュームは別途保全してください。

# イメージとメタデータの保全
docker image inspect <image-id> > image-inspect.json
docker image save -o litellm-image.tar <image-id>
sha256sum litellm-image.tar > litellm-image.tar.sha256

# 停止済みの調査対象コンテナがある場合
docker container inspect <container-id> > container-inspect.json
docker export -o container-fs.tar <container-id>

イメージ内のファイルは、docker create で一時コンテナを作成し、docker export で一覧化して確認できます。docker create はコンテナを作成するだけで、エントリーポイントを実行しません。タグ名を指定するとレジストリから別のイメージを取得するおそれがあるため、ローカルのイメージ ID を指定し、--pull=never で取得を抑止します。

cid=$(docker create --pull=never <image-id>)
docker export "$cid" | tar -tf - | grep -E 'site-packages/(cook\.pth|cook\.py|sitecustomize\.py|litellm-1\.107\.0\.dist-info/)'
docker rm "$cid"

Kubernetes では、コンテナごとに参照したイメージ名(image)と、実際に実行したイメージの識別子(imageID)を確認できます。次のコマンドは、init コンテナを含む全コンテナを 1 行ずつ出力し、Pod 名ではなくイメージ名で絞り込みます。社内ミラー経由で取得している場合は、awk の条件をミラー上のイメージ名に合わせてください。

kubectl get pods -A -o go-template='[6]range .items[7]$ns := .metadata.namespace[8]$pod := .metadata.name[9]$ts := .metadata.creationTimestamp[10]range .status.initContainerStatuses[11]$ns[12]"\t"[13]$pod[14]"\t"[15]$ts[16]"\t"[17].image[18]"\t"[19].imageID[20]"\n"[21]end[22]range .status.containerStatuses[23]$ns[24]"\t"[25]$pod[26]"\t"[27]$ts[28]"\t"[29].image[30]"\t"[31].imageID[32]"\n"[33]end[34]end' \
  | awk -F'\t' 'tolower($4) ~ /berriai\/litellm|litellm\/litellm/'

このコマンドで分かるのは、現在存在する Pod だけです。影響時間帯以降に再作成・削除された Pod は表示されないため、Kubernetes の監査ログ、ノードのコンテナランタイムのログ、レジストリやプルスルーキャッシュのアクセスログで、当時の取得と起動を確認してください。Kubernetes の Event は既定で 1 時間程度しか保持されないため、Event だけに依存しない調査が必要です。

ホスト・仮想マシンのファイルを確認する

稼働中のホストで確認する場合は、Python を起動しないシェルコマンドを使います。root 権限で実行し、出力はホストの外へ調査記録として保存します。

# site-packages 内の痕跡を検索(Python は起動しない)
find / -path /proc -prune -o -path '*site-packages*' \( -name 'cook.pth' -o -name 'cook.py' -o -name 'sitecustomize.py' -o -name 'cook*.pyc' -o -name 'sitecustomize*.pyc' -o -name 'litellm-1.107.0.dist-info' \) -print 2>/dev/null

# ロックファイルと、それを保持しているプロセス
ls -l /tmp/remote_code_loader.lock
fuser -v /tmp/remote_code_loader.lock

停止できる仮想マシンは、スナップショットから作成したディスクを調査用インスタンスへ接続し、読み取り専用でマウントして同じ検索を行う方法を推奨します。ext4 ではジャーナルの再生を避ける noload、XFS では norecovery を併用します。同じイメージから作成したインスタンスで XFS をマウントする場合は、nouuid も必要になることがあります。

# ext4 の例(<device> は調査用に接続したディスク)
mount -o ro,noload <device> /mnt/evidence

# XFS の例
mount -o ro,norecovery <device> /mnt/evidence

find /mnt/evidence -path '*site-packages*' \( -name 'cook.pth' -o -name 'cook.py' -o -name 'sitecustomize.py' -o -name 'litellm-1.107.0.dist-info' \) -print

公開された 7 個の wheel には、Windows 向けと macOS 向けも含まれます。開発者の PC は、Python の仮想環境ごとの site-packages を同様に確認してください。Windows では PowerShell で次のように検索できます。

Get-ChildItem -Path C:\ -Recurse -Force -ErrorAction SilentlyContinue -Include cook.pth,cook.py,sitecustomize.py | Where-Object { $_.FullName -like '*site-packages*' }

調査に使う記録の優先順位

攻撃者は LiteLLM 側のワークフロー実行記録を削除しており、画面に表示される履歴が当時の状態を反映しているとは限りません。利用者側の調査でも、現在の表示だけで判断せず、保存期間の短い記録から先に保全することを推奨します。

優先度記録目的
高コンテナ・Pod の作成時刻、実行された image digest、ノードのランタイムログ影響イメージの実行有無と時刻の特定
高ホスト・コンテナの外向き通信ログ(プロキシ、DNS、ファイアウォール、VPC フローログ)情報流出の可能性と通信先の確認
高CI/CD のジョブ履歴と依存関係の解決ログ影響時間帯にインストールしたジョブと、そのジョブに渡された秘密情報の特定
高AWS CloudTrail などのクラウド監査ログ窃取された可能性がある認証情報の利用有無の確認
中パッケージの取得履歴(社内ミラー、プロキシ、pip / uv のキャッシュ)取得の事実と、キャッシュからの再配布の有無の確認
中コンテナレジストリ・プルスルーキャッシュのアクセスログイメージ取得の事実と範囲の確認
中GitHub の audit log と workflow run自組織のトークンが不正に使われた形跡の確認
中LiteLLM と実行環境が参照できた秘密情報の一覧ローテーション範囲の確定

GitHub Actions のログは既定で 90 日保持されますが、リポジトリや組織の設定で短縮されている場合があります。影響時間帯のジョブはログをエクスポートして保全してください。CloudTrail について、公式はインストール時刻前後にインスタンスロールによる GetCallerIdentity がないかを確認し、その後、自社インフラ外から同じロールの認証情報が使われていないかを確認するよう案内しています。

第三者の StepSecurity は、PyPI での除去後も一部のミラーがキャッシュ済みの 1.107.0 を配信していたと報告しています。社内のパッケージミラーやキャッシュプロキシを運用している場合は、1.107.0 のファイルが残っていないか確認してください。通信先ドメインやファイルハッシュなどの IoC は、本記事に転載せず、公式資料と第三者分析の最新版を参照することを推奨します。

侵害が疑われる場合の封じ込めと復旧

影響を受けた可能性がある環境は、パッケージの削除や更新ではなく、証拠保全、隔離、秘密情報の無効化、クリーンな再構築の流れで対応します。緊急の認証情報の失効は、証拠保全や再構築の完了を待たずに並行して進めます。

手順
影響の疑いを検知する

前節の確認で、影響時間帯のインストール、影響 digest の実行、痕跡ファイルのいずれかを確認した場合は、インシデントとして扱い、関係者と対応範囲を共有します。

手順
証拠を保全し、環境を隔離する

停止する前に、プロセス一覧、/tmp/remote_code_loader.lock を保持するプロセスの情報、ネットワーク接続の状況を記録し、可能であればディスクのスナップショットを取得します。その後、外向き通信を遮断したネットワークへ隔離します。メモリ取得の体制がある組織では、常駐プロセスを停止する前の取得を検討してください。<pid> は fuser で確認したプロセス ID です。

ps -eo pid,ppid,user,lstart,args
ss -tnp
ls -l /proc/<pid>/exe /proc/<pid>/cwd
tr '\0' ' ' < /proc/<pid>/cmdline
手順
影響の段階を判定する

アーティファクト、実行履歴、通信履歴から、取得、インストール、実行、流出のどの段階まで進んだかを判定します。公式は、外向き通信が可能なホストにインストールされていた場合は、持ち出しが成功した前提で対応するよう求めています。

手順
信頼できる管理環境から秘密情報を無効化する

感染環境とは別の、信頼できる管理端末から失効とローテーションを実施します。感染環境の中で新しい認証情報を発行・設定すると、再び窃取される可能性があります。対象範囲は、次の「秘密情報ローテーションの範囲」を参照してください。

手順
クリーンなイメージ・仮想環境から再構築する

公式は、常駐デーモンの停止と悪性ファイルの削除をシェルから行ったうえで 1.106.2 に固定して再インストールする手順を示し、イメージや仮想環境を一から作り直すほうが望ましいとしています。更新先のバージョンは、前述の公式リリース情報で最新の案内を確認して決めます。運用環境では、侵害されたホストやコンテナを修復して使い続けるのではなく、信頼できるベースイメージから再構築し、LiteLLM のイメージは digest で固定することを推奨します。

手順
再感染を防止し、動作を確認する

pip / uv のキャッシュ、社内ミラー、レジストリやノードに残る影響イメージを削除し、依存指定とロックファイルから 1.107.0 を除外します。再構築後の環境で前節の痕跡が存在しないことと、新しい認証情報で正常に動作することを確認します。

手順
監視を強化してサービスを再開する

失効前の認証情報の利用、想定外の外向き通信、クラウド API の異常な呼び出しを一定期間監視しながら、サービスを再開します。

pip uninstall や新しいバージョンへの更新だけでは、すでに実行された悪性コードの影響(窃取された認証情報や常駐プロセス)は取り除けません。pip の実行自体も Python を起動するため、パッケージ操作の前にシェルでの確認と認証情報の失効を進めてください。

秘密情報ローテーションの範囲

公式は、ホストから到達可能だった秘密情報をすべてローテーションするよう求めています。第三者分析によると、マルウェアはクラウドのメタデータ、プロセスの環境変数、各種設定ファイル、LiteLLM プロキシのデータベースなど、広い範囲を収集対象としています。対象範囲は、悪性コードを実行したプロセスの権限と、その環境から読み取り・到達できた秘密情報を基準に決めます。

分類対象例対象とする条件
IAM role / クラウド認証情報インスタンスロール、タスクロール、アクセスキー、~/.aws などの認証ファイル実行環境からメタデータサービスや認証ファイルに到達できた場合。公式はインスタンス / タスクの IAM role を明示している
LLM プロバイダーの API keyLiteLLM プロキシの DB に保存されたプロバイダーキー、環境変数のキー公式は、プロキシ DB に保存されたすべてのプロバイダーキーを対象としている
LiteLLM の master key / virtual keymaster key、virtual keyLiteLLM プロキシとして稼働していた場合
DB 認証情報DATABASE_URL のパスワード環境変数として渡されていた場合
CI/CD secretsパッケージ公開トークン、デプロイ用の認証情報、レジストリの認証情報影響時間帯に 1.107.0 をインストールしたジョブ、または同じランナーで動作したジョブに渡されていた場合
GitHub CLI OAuth tokengh の認証情報、Personal Access Token開発者の PC や CI ランナーに保存されていた場合
環境変数の秘密情報コンテナ・Pod・ホストのプロセスに渡された各種キー公式は、ホスト上のあらゆるプロセスの環境にあった秘密情報を対象としている
その他の読み取り可能な認証情報SSH 鍵、kubeconfig、.env、サービスアカウントトークン、AI コーディングツールの認証情報実行ユーザーの権限で読み取れた場合(第三者分析の収集対象に基づく)

実行ユーザーが root か、コンテナが特権で動作していたか、ホストのディレクトリをマウントしていたかによって、到達可能な範囲は大きく変わります。判断材料が不足する場合は範囲を狭く見積もらず、到達できた可能性があるものを対象に含めることを推奨します。

CI/CD と秘密情報管理で見直すべき対策

今回の侵害は、利用者側の依存関係の管理と、公開側のリリース管理の両方に教訓を残しています。ここでは、自組織で実施できる対策を中心に整理します。

依存関係とイメージを固定し、新しい版の取り込みを遅らせる

Python パッケージはロックファイルでバージョンを固定し、コンテナイメージはタグではなく digest で参照します。あわせて、社内ミラーやパッケージ管理の仕組みで、公開直後のバージョンを一定期間取り込まない運用にすると、短時間で除去される悪性リリースの影響を受けにくくなります。なお、ハッシュによる固定は固定時点のファイルとの一致を確認するものであり、悪性版を固定してしまった場合は防げません。

漏洩した可能性のある鍵は削除ではなく失効させる

今回の起点は、コミットの上書き後もマスターキーが失効されなかったことでした。公開リポジトリへ秘密情報をコミットした場合、履歴の書き換えでは第三者が取得済みのコピーを無効化できません。発見した時点で失効と再発行を行い、その完了を確認する手順をインシデント対応に組み込むことを推奨します。

内部ツールに強い権限の個人トークンを持たせない

Agent Platform には、サービスアカウントではない組織 owner の GitHub CLI OAuth token が、権限を絞らずに設定されていました。確定調査は、GitHub CLI のトークンが機密性の高い操作で求められる再認証の対象外である点にも触れています。AI エージェントなどの内部ツールに GitHub を操作させる場合は、対象リポジトリと権限を限定した GitHub App や専用アカウントを使い、個人のトークンを設定しないことを推奨します。

リポジトリ保護の変更と承認操作を監視する

branch protection の無効化、保護ルールの上書き、force push、workflow run の削除は、通常の開発ではまれな操作です。GitHub の audit log をログ基盤へ転送し、これらの操作を通知対象にすることを推奨します。リリース承認者は限定し、一時的に制限を緩和する場合も、期限と監視を設けてください。

CI ランナーの外向き通信と再利用を制限する

依存関係をインストールするジョブは、パッケージレジストリなど必要な通信先だけを許可し、ジョブごとに使い捨てのランナーを使う構成を推奨します。悪性パッケージがインストールされた場合でも、情報の持ち出しや常駐を抑えやすくなり、影響を受けたジョブと秘密情報の特定も容易になります。

まとめ

LiteLLM 1.107.0 の侵害は、公開リポジトリに残ったマスターキーを起点に、正規のリリースワークフローが攻撃者の fork から署名付きの成果物を公開した事案です。影響時間帯に取得・インストール・実行した環境は、Python を起動せずに痕跡と記録を確認し、秘密情報の失効とクリーンな再構築を並行して進める必要があります。

  • PyPI の 1.107.0 と、Docker Hub・GHCR の 4 タグが影響対象
  • 現在のタグではなく、当時取得・実行した digest で判定する。
  • 感染が疑われる環境では pip show を含め Python を起動しない。
  • 痕跡が見つからないことは、安全であることの証明にならない。
  • 認証情報の失効は、証拠保全や再構築と並行して進める。
  • pip uninstall や更新だけで終えず、クリーンな環境から再構築する。
  • 3 月の 1.82.7 / 1.82.8 とは起点も痕跡も異なり、手順は流用しない。

以上、最後までお読みいただきありがとうございました。

脚注

脚注
↑1 .Id
↑2 json .RepoDigests
↑3 .ID
↑4 .CreatedAt
↑5 .Status
↑6 range .items
↑7 $ns := .metadata.namespace
↑8 $pod := .metadata.name
↑9 $ts := .metadata.creationTimestamp
↑10 range .status.initContainerStatuses
↑11, ↑23 $ns
↑12, ↑14, ↑16, ↑18, ↑24, ↑26, ↑28, ↑30 "\t"
↑13, ↑25 $pod
↑15, ↑27 $ts
↑17, ↑29 .image
↑19, ↑31 .imageID
↑20, ↑32 "\n"
↑21, ↑33, ↑34 end
↑22 range .status.containerStatuses
よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

関西を拠点に活動する、現役インフラエンジニア。経験20年超。

大手通信キャリアにて、中〜大規模インフラ(ネットワーク・サーバ・クラウド・セキュリティ)の設計・構築およびプロジェクトマネジメントに従事。現場で直面した技術課題への対処や、最新の脆弱性情報への実務対応を、一次情報として発信しています。

保有資格
CCIE Lifetime Emeritus(取得から20年以上)/ VCAP-DCA / Azure Solutions Architect Expert

▶ 運営者プロフィール(詳細)

目次