LiteLLM 1.107.0 が PyPI・Docker で改ざん — 認証情報窃取とバックドアの影響確認と対処手順
2026年10月21日、LiteLLM 1.107.0がPyPIとDockerで改ざんされ、認証情報窃取とバックドアが混入した。影響はwheelとコンテナで1.106.2は安全。Pythonを動かさずにできる確認コマンド、隔離から全認証情報ローテーションまでの手順、再発防止、3月の事件との違いを整理する。
2026年10月21日(UTC)、LLM ゲートウェイ/SDK の LiteLLM のバージョン 1.107.0 が PyPI と Docker(Docker Hub・GHCR)で改ざんされ、認証情報を盗むマルウェアとバックドアが混入した。対象は 1.107.0 の wheel またはコンテナイメージを導入・実行したすべての環境で、今すぐ litellm==1.107.0 と該当 Docker タグの使用有無を確認し、該当すれば Python を再実行せずに隔離して、到達可能だった認証情報をすべて失効・再発行する必要がある。
本記事は2026年10月22日時点で公開されている SafeDep、StepSecurity、JFrog、Endor Labs、Socket、Chainguard の調査記事と、LiteLLM 側の GitHub issue のタイトルに基づく。BerriAI(LiteLLM の開発元)の詳細な公式報告は、本稿執筆時点で本文を確認できていない。そのため、公式に明記が確認できない点は「公式に明記なし」「情報源により差がある」と書き分ける。事態は進行中で、数字や時刻は今後更新される可能性がある。
何が起きたのか — 3行でわかる要点
- 何が: LiteLLM 1.107.0 の PyPI 配布物とコンテナイメージに、ソースコードリポジトリには存在しない悪意あるファイルが含まれていた
- どう動くか: Python を起動するたびに実行される仕組みで、litellm を import しなくても動く。SSH 鍵、クラウド認証情報、AI ツールのトークンなどを暗号化して外部へ送信する
- いま何をすべきか: 1.107.0 の使用有無を確認し、該当すれば隔離・再構築・認証情報の全ローテーションを行う。安全な最新版は 1.106.2 で、1.107.0 には戻らない
影響を受けるバージョンと安全なバージョン
影響範囲は、情報源間でほぼ一致している。ただし sdist(ソース配布物)の扱いに差がある。SafeDep は sdist も影響対象に含めて記載する一方、StepSecurity・JFrog・Endor Labs・Socket・Chainguard は「sdist にはペイロードのファイルは入っていないが、ビルド設定に不正な1行が追加されている」と説明している。実害が出るのは wheel とコンテナイメージなので、実務上は wheel とイメージを優先して確認すればよい。
| 対象 | 状態 | 備考 |
|---|---|---|
PyPI litellm==1.107.0 の wheel 7個 | 悪意あり | macOS(x86_64・arm64)、manylinux/musllinux(x86_64・aarch64)、Windows amd64 |
PyPI litellm==1.107.0 の sdist | 情報源により差がある | ペイロードのファイルは無いが、pyproject.toml に不正な設定が入っている(多数の情報源)。PyPI からは削除済み |
Docker v1.107.0(Docker Hub・GHCR の litellm、litellm-database、litellm-non_root) | 悪意あり | 複数の情報源が記載。イメージの種類は情報源により列挙が異なる |
Docker latest / main-latest / main-stable | 悪意あり(約3時間) | UTC 11:46〜14:20 頃に不正なイメージを指していた。現在は差し替え・削除済み |
litellm==1.106.2 | 安全 | 複数社が最後の正常版として推奨 |
1.107.0rc1 / 1.107.0rc2 | 安全 | 検査して正常を確認したと報告(StepSecurity、Chainguard) |
litellm-proxy-extras 0.4.110 / litellm-enterprise 0.1.78 | 安全 | 正常と報告 |
ここで重要なのは、GitHub 上の v1.107.0 タグのソースコードは正常だという点である。コードレビューやタグの差分確認では見つからず、PyPI と Docker に出た成果物だけが汚染されていた。「ソースを読んだから安全」という判断が通用しない典型例になっている。
時系列
時刻は2026年10月21日(UTC)。日本時間(JST)は +9 時間で、たとえば PyPI への公開 11:39 UTC は 20:39 JST にあたる。なお、情報源ごとに数分〜数十分の差があるため、主な出来事を表にまとめる。
| 時刻(UTC) | 出来事 | 出典・補足 |
|---|---|---|
| 10月13日 | 偽ドメイン litellm.dev が登録される | StepSecurity。攻撃の準備は数日前から始まっていた |
| 09:48〜10:46 | 偽リポジトリ Berri-AI/litellm(正規は BerriAI/litellm)に不正なコミットが入る | StepSecurity は 08:56〜10:46 に10コミット、Endor Labs は 09:48 の最初のコミットと 10:46 の Dockerfile 変更を記載 |
| 10:08 | ブランチ保護が上書きされ、リリース workflow が改変される | SafeDep の記載。他の情報源では同時刻の記載を確認できていない |
| 10:51〜11:06 | コンテナのビルドが偽リポジトリのコミットから実行される | Endor Labs・Socket。ビルド来歴に所有者「Berri-AI」と記録 |
| 11:39〜11:40 | 悪意ある wheel 7個が PyPI にアップロードされる | SafeDep・Endor Labs・Chainguard |
| 11:46〜11:48 | 悪意あるコンテナイメージが公開され、浮動タグが差し替わる | SafeDep は 11:46、StepSecurity は 11:48 |
| 11:46〜11:55 | 各社のスキャナが検知 | Socket は約7分後、Chainguard は約16分後に検知 |
| 12:06 | GitHub issue #47826 で侵害が報告される | StepSecurity。issue のタイトルに「PyPI removed; Docker latest/main-stable removed」とある |
| 13:23 | PyPI が 1.107.0 を削除(隔離) | SafeDep・StepSecurity |
| 14:20〜14:32 | Docker のタグが削除または正常なイメージへ差し替えられる | SafeDep は 14:20、StepSecurity は Docker イメージ削除を 14:32 と記載 |
| 10月22日 | LiteLLM が「封じ込め済み」と発表し、更新を公開 | JFrog の記載。内容の詳細は未確認 |
PyPI での露出は約1時間45分(11:39〜13:23 UTC)、Docker の浮動タグはおよそ3時間である。短時間に見えるが、LiteLLM は月間のダウンロード数が1億を超え、2,000を超えるパッケージが依存すると Chainguard は記載している。CI が毎回最新版を取得する構成や、latest タグでコンテナを再起動する運用では、この時間内に取り込まれていてもおかしくない。
攻撃の仕組み — import しなくても動く理由

攻撃の起点は、sdist の pyproject.toml の [tool.maturin] 設定に追加された data = "cook" という1行である。この設定により、cook/ ディレクトリの中身が wheel のインストール時に site-packages の直下へ配置される。結果として、次の3ファイルが litellm パッケージとは別に、Python の標準の探索パスに置かれる。
- cook.pth(12バイト): import cook の1行だけを含む。実体は cook.py の読み込み指示
- cook.py(RECORD 上で約22 KB): Base64+zlib で隠蔽された約61 KB の認証情報窃取プログラム(privcollect と呼ばれている)
- sitecustomize.py(25,509バイト): バックドア(リモートコード実行ローダー)と、予備の窃取コードを含む
鍵になるのは Python の起動時の仕様である。Python は起動時に site-packages 内の .pth ファイルを走査し、import で始まる行を実行する。さらに sitecustomize という名前のモジュールが存在すれば、それも自動で import する。つまり悪意あるコードは、litellm を import しなくても、その環境の Python プロセスが1つ起動するたびに実行される。pip の実行、IDE の言語サーバ、テストランナー、無関係なスクリプトも例外ではない。「LiteLLM はこのサーバでは使っていない」と思っていても、同じ仮想環境にインストールされていれば発火する。
なお、配布元の経路についても整理しておく。コンテナのビルド来歴には、正規の BerriAI/litellm ではなく、ハイフン入りの偽リポジトリ Berri-AI/litellm が所有者として記録されていた。StepSecurity によれば、攻撃者のコミットは GitHub の Web エディタ経由で作られたため「Verified」バッジが付いており、これだけでは作成者の正当性を示さない。偽リポジトリは現在 404 を返す。一方で、どうやって正規のリリース workflow が偽リポジトリを参照するようになったのかは、情報源により説明が割れている。
- SafeDep は、LiteLLM の報告として「開発者3人分の GitHub CLI 認証情報が、インターネットに公開されていた社内環境のログから攻撃者に渡った可能性が高い」「ブランチ保護を迂回し、2人目の開発者の認証情報で承認ゲートを通過した」と記載している
- Socket と Endor Labs は、上書きがどう承認されたか、認証情報が侵害されたかどうかは確立できていないとしている
- StepSecurity は、BerriAI がリリースの公開経路をまだ調査中だと記載している
- JFrog は、根本原因(ビルド、CI、公開のどこが侵害されたか)は不明としている
この点は、BerriAI の公式な事後報告が出るまで確定しない。本稿では「原因は調査中で、情報源によって説明に差がある」と扱う。
ペイロードの動作 — 窃取、送信、居座り
第1段階(窃取)では、cook.py が別スレッドで 16 の収集モジュールを動かす。多重実行を避けるため、環境変数 PRIVCOLLECT_ACTIVE=1 を設定する。収集した結果は JSON にまとめ、AES-256-CBC で暗号化し、その鍵を埋め込みの RSA-4096 公開鍵(OAEP)で包んだうえで、tar.gz にして http://www.litellm[.]dev/healthcheck へ HTTP POST する(X-Token ヘッダ付き、最大4回再試行)。このドメインは正規の litellm.ai に似せた偽物で、通信は暗号化されていない平文 HTTP である。
送信が HTTP 200 で成功すると、/tmp/.pc_once_72129118c59f5636c0bb というマーカーファイルを作り、その後は再実行しない。裏を返せば、マーカーが無いことは感染していない証拠にならない。送信に失敗した場合、コンテナを作り直した場合、そして Windows でパスが解決できない場合は、マーカーが残らず、Python が起動するたびに再び収集を試みる。
第2段階(居座り)では、sitecustomize.py が /tmp/remote_code_loader.lock でロックを取り、fork と setsid() でデーモン化して端末から切り離す(標準入出力は /dev/null、作業ディレクトリは /)。その後は1時間ごとに、GitHub 上の letters9813/pythonclient-test リポジトリの domain.txt からホスト名を読み、窃取コードを再実行し、http://<ホスト名>/codepy から Python コードを取得して 署名や整合性の検証なしに exec() する。つまり、攻撃者が任意のコードを後から送り込める経路になっている。
ただし現状の第2段階は、少なくとも観測時点では武装解除されていたと報告されている。domain.txt は空(0バイト)で、SafeDep は GitHub アカウント letters9813 が 404 になったことを確認し、攻撃者のサーバのアドレスは回収できなかったとしている。過去の値は localhost:18909 や test.test.com:18909 で、本番サーバだったとは断定されていない。注意点が2つある。1つは、ローダーを止めても第1段階の窃取(cook.pth 経由)は止まらないこと。もう1つは、StepSecurity が指摘するとおり、感染ホストは削除済みのアカウント名の URL を見続けるため、第三者が同名のアカウントとファイルを再作成すれば、感染ホストでコードを実行させられる可能性があることである。感染ホストを放置してよい理由にはならない。
プラットフォームにより挙動が違う。Windows では fcntl と os.fork が使えないため、バックドアのローダーは黙って失敗するが、窃取自体は動く。macOS では、Socket の分析によれば LiteLLM のデータベース収集モジュールが通常の環境で失敗し、Chromium 系のパスワード復号も平文では取れないが、ブラウザのデータベースファイル自体はコピーされ、ほかのモジュールは有効である。
盗まれる情報
収集対象は広く、開発者のワークステーション、CI ランナー、LiteLLM プロキシのサーバのいずれでも、その Python プロセスから読めたもの全部が対象になる。主な項目は次のとおり。
- 鍵類: SSH 秘密鍵、GPG 秘密鍵、TLS 秘密鍵、.pem/.key/.p12/.pfx ファイル
- クラウド・コンテナ: AWS、Azure、Google Cloud の認証情報、Kubernetes の設定とサービスアカウントトークン、Docker と Swarm のシークレット、GitHub CLI の認証情報
- クラウドのメタデータ: IMDS(169.254.169.254)やECS/GCP/Azure のメタデータ経由の一時認証情報。AWS キーは GetCallerIdentity で有効性も検証される
- CI/インフラ: GitLab Runner、Jenkins、Vault、Terraform、Packer の秘密情報、/run/secrets
- パッケージ・Git: .git-credentials、.npmrc、.pypirc、.netrc、.env ファイル
- AI ツール: Claude、Cursor、Copilot、Codex、Gemini、Windsurf、Kiro、Zed、Continue、Hugging Face などの設定とトークン(~/.claude/.credentials.json、~/.codex/auth.json など)
- ブラウザ: Chromium 系と Firefox のパスワード、Cookie、履歴
- 履歴・環境変数: シェル履歴、データベースのクライアント履歴、自プロセスと(Linux では)読めるすべてのプロセスの環境変数(/proc/<pid>/environ)
- LiteLLM プロキシ: DATABASE_URL が設定されていれば、LiteLLM_CredentialsTable と LiteLLM_ProxyModelTable を丸ごと取得する。ここには、プロキシ配下の全チームが使うLLM プロバイダの API キーが入っている
LiteLLM は複数の LLM プロバイダのキーを集約するゲートウェイとして使われることが多く、1台のプロキシが侵害されると、OpenAI、Anthropic、Google など配下のすべてのプロバイダのキーが一度に漏れる。AI ツールのトークンまで狙う点は、開発者個人のマシンで LiteLLM を使うケースでも、被害がクラウドや社内システムに波及しうることを意味する。AI エージェントを使った開発の運用については、Claude Code とエージェント SDK の解説も参考になる。
影響確認の手順
最初に守るべき原則は、疑わしい環境で Python や pip を実行しないことである。pip show や pip uninstall を含め、インタプリタを起動するたびに cook.pth が発火し、窃取が再試行されるためだ。Chainguard、JFrog、Endor Labs は、シェルコマンドなどインタプリタを使わない手段で調べることを求めている。次のコマンドはどれも Python を使わない。Linux/macOS で、可能なら調査専用のマシンから対象を読み取り専用でマウントして実行するのが望ましい。
# 1. 依存の記述に 1.107.0 が無いか(リポジトリのルートで)
grep -rnE 'litellm.{0,6}1\.107\.0' requirements*.txt pyproject.toml poetry.lock uv.lock Pipfile.lock 2>/dev/null
# 2. インストール済みのメタデータ(Python 不要)
find / -xdev -type d -name 'litellm-1.107.0*' 2>/dev/null
# 3. 悪意ある3ファイルの有無(site-packages 配下)
find / -xdev -type f \( -name cook.pth -o -name cook.py \) -path '*site-packages*' 2>/dev/null
find / -xdev -type f -name sitecustomize.py -path '*site-packages*' 2>/dev/null
# 4. 見つかったファイルをハッシュで照合(sitecustomize.py は正規の環境にも存在し得る)
sha256sum <見つかったファイル>
# cook.pth 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb
# cook.py 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25
# sitecustomize.py 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa
# (Windows 版は改行コードの違いで別のハッシュ。各社の記事を参照)
# 5. 実行の痕跡(マーカー、ロック、一時ファイル)
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock /tmp/.pc_sq_* /tmp/.pc_e_* 2>/dev/null
# 6. バックドアの常駐(ロックを保持しているプロセス)
lsof /tmp/remote_code_loader.lock 2>/dev/null
ps -eo pid,ppid,sid,tty,args | awk '$2==1 && /python/'
# 7. 環境変数 PRIVCOLLECT_ACTIVE=1 を持つプロセス(Linux)
grep -l 'PRIVCOLLECT_ACTIVE=1' /proc/[0-9]*/environ 2>/dev/null
# 8. pip/uv のキャッシュに 1.107.0 が残っていないか
find ~/.cache/pip ~/.cache/uv -iname '*litellm-1.107.0*' 2>/dev/null
# 9. Docker:ローカルのイメージと稼働中コンテナのダイジェスト
docker images --digests | grep -i litellm
docker ps --format '{{.ID}} {{.Image}}' | grep -i litellm
docker inspect --format '{{.Image}} {{join .RepoDigests " "}}' <イメージ名>
# 悪意あるイメージのダイジェスト
# index: sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e
# amd64: sha256:bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5
# arm64: sha256:56b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdf
# 10. Kubernetes:稼働中の Pod が使うイメージID
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" "}{.status.containerStatuses[*].imageID}{"\n"}{end}' | grep -iE 'litellm|1bbcb144|bd1aeee4|56b74573'上のダイジェストは Chainguard と StepSecurity が公表したもので、litellm-database と litellm-non_root の各イメージには別のダイジェストがある(Chainguard の記事に一覧)。確認は完全なハッシュで行うこと。また、ネットワークログでは www.litellm.dev への通信、raw.githubusercontent.com/letters9813/ へのアクセス、/codepy で終わるパスへのリクエストを探す。/codepy へのリクエストが1件でもあれば、第2段階のコードが実際に実行された可能性があり、被害の判断が一段重くなる。GitHub 全体のブロックは業務への影響が大きいため、ブロックはこのリポジトリのパスに限定する。
マーカーが無くても安心はできない。前述のとおり、送信に失敗したケースやコンテナの作り直しではマーカーが残らない。1.107.0 を実際にインストール・実行した事実が確認できた時点で、感染として扱うのが安全である。CI のログとキャッシュは 10 月 21 日分を見直し、latest などの浮動タグでコンテナを起動する構成は、その日に取得したイメージのダイジェストを確認する。
感染していた場合の対処 — 隔離、再構築、全ローテーションの順
対処の順番が重要である。先に認証情報を回しても、感染環境が残っていれば新しい認証情報も盗まれる。次の順で進める。
1. 隔離する: 該当のホスト、コンテナ、CI ランナーをネットワークから切り離す。可能なら調査用にディスクやコンテナを保全し、DNS・プロキシ・EDR・クラウド監査ログを保存する
2. 常駐を止める: /tmp/remote_code_loader.lock を保持するプロセスや、親が PID 1 で作業ディレクトリが / の Python プロセスを終了させる
3. ファイルを手で消す: cook.pth、cook.py、対応する __pycache__、マーカー(/tmp/.pc_once_...)とロックを、シェルで削除する。pip uninstall は Python を起動して再発火させ、しかも RECORD に頼るため使わない。sitecustomize.py は正規の環境にも存在するので、中身かハッシュを確認してから削除する(ディストリビューション付属のものを消すと別の不具合を起こす)
4. 再構築する: 可能なら、その場の掃除ではなく、検証済みの成果物から環境、イメージ、ホストを作り直す。litellm==1.106.2(または検証したバージョン)を、後述のハッシュ固定で入れる。社内のミラーやプロキシ、レジストリにキャッシュされた 1.107.0 も消し、取得をブロックする
5. 認証情報を全部ローテーションする: 必ずきれいな別のマシンから行う。対象は後述の一覧のとおり
6. 悪用の痕跡を調べる: 10月21日(UTC)以降のクラウド、リポジトリ、レジストリ、ID 基盤の監査ログで、見覚えのない API 呼び出し、新しい鍵やトークンの作成、ロールの変更を探す
7. 通信を遮断する: litellm.dev(www.litellm.dev)を DNS かエグレスでブロックし、/codepy へのリクエストを検知対象に入れる
ローテーションの対象は、そのプロセスから読めた可能性のあるものすべてと考える。パッケージを消しても、すでに送信された情報は戻らない。最低限、次を見直す。
- クラウドの鍵・ロール(AWS、Azure、Google Cloud)、インスタンスロール、Kubernetes と Vault のトークン
- SSH、GPG、TLS の鍵(公開側の authorized_keys や証明書の失効・再発行も含む)
- GitHub、GitLab などの個人アクセストークン、デプロイキー、レジストリ(PyPI、npm、Docker)のトークン
- CI/CD のシークレット、.env の値、環境変数に置いていた値
- Claude、Cursor、Copilot、Codex などの AI ツールのトークンとセッション
- ブラウザに保存していたパスワードと、ログイン済みのセッション
- LiteLLM プロキシでは、マスターキー、ソルトキー、仮想キー、LiteLLM_CredentialsTable と LiteLLM_ProxyModelTable にある全プロバイダの API キー、DATABASE_URL のデータベース認証情報
なお、LiteLLM のプロキシで LITELLM_SALT_KEY を変更すると、データベースに暗号化して保存していた既存のキーが復号できなくなる点には注意が要る。変更の手順は LiteLLM のドキュメントで確認し、キーの再登録の手間を見込んでおくこと。
再発防止 — 今日からできる固定と遅延
今回の件で重要なのは、ソースが正しくても配布物が汚染されうる、そして一度のインストールが全認証情報の漏えいにつながる、という2点である。組織側でできる対策を、効果の大きい順に並べる。
- バージョンとハッシュを固定する: litellm>=1.100 のような範囲指定をやめ、== で固定したうえで、ハッシュ付きでインストールする。pip-compile --generate-hashes で要件ファイルを作り、pip install --require-hashes -r requirements.txt で入れる。uv なら uv.lock をコミットして uv sync --frozen を使う。ハッシュ固定なら、同じバージョン番号で中身が差し替わっても検知できる(ただし、今回のように新しいバージョンが出た後に更新する時は、別途の確認が要る)
- Docker は、タグではなくダイジェストで固定する: latest や main-stable などの浮動タグは、今回のように中身が変わる。image: ghcr.io/berriai/litellm@sha256:<確認済みのダイジェスト> の形で参照し、更新は人が確認してから行う
- 新しいバージョンの採用を数日遅らせる: 今回、悪意ある版が公開されてから削除されるまでは約1時間45分だった。公開から3〜7日待つ運用にすれば、ほとんどの検知と削除が間に合う。uv の exclude-newer や、Renovate の minimumReleaseAge、Dependabot の cooldown が使える
- CI のエグレスを絞る: ビルドとテストのジョブから、許可リスト以外への外向き通信を遮断する。今回の窃取は www.litellm.dev への HTTP POST なので、許可リスト方式なら送信が失敗する。StepSecurity の Harden-Runner のようなツールで、通信のベースラインを取る方法もある
- CI の秘密情報を最小にする: テストやビルドの環境に、クラウドの本番鍵や公開用トークンを置かない。OIDC による短寿命のトークンに切り替え、シークレットは必要なジョブにだけ渡す
- 起動フックを監視する: 第三者の wheel に新しい .pth や sitecustomize.py が追加された時にアラートを出す。Chainguard は、ビルド設定([tool.maturin]、[tool.setuptools])の data 項目の追加も検知対象に挙げている
- 開発者の端末と本番を分ける: LiteLLM プロキシは、AI ツールのトークンや個人の SSH 鍵があるマシンと別の、最小権限の環境で動かす
- メンテナ側の対策(自分でパッケージを公開する場合): PyPI の Trusted Publishing(OIDC)で長期トークンをなくし、公開できるリポジトリとワークフローを固定する。リリース用ワークフローは、承認済みのリポジトリと、レビュー済みのコミットだけから動くよう制限する。公開物と、レビューしたソースの差分を照合する
依存関係の更新を遅らせる運用は、一方で脆弱性の修正が遅れるという代償を伴う。同じ時期の PHP の認証不要 RCE のように、すぐ当てるべき修正もある。重大な脆弱性の修正は例外として扱い、通常の更新は遅延、緊急の修正はすぐ適用という2系統のルールにしておくとよい。AI 関連の基盤をまとめて見直す視点としては、Stripe による OpenRouter 買収の解説が扱う、LLM ゲートウェイを巡る動向も参考になる。
3月の事件との比較
LiteLLM が2026年に侵害されるのは今回が2度目で、JFrog の記事の見出しは「Hijacked Again」である。3月には、バージョン 1.82.7 と 1.82.8 が改ざんされた。JFrog はこの2つを別のキャンペーンとして扱い、それぞれの IoC で調べるべきだとしている。
| 項目 | 2026年3月(1.82.7 / 1.82.8) | 2026年10月(1.107.0) |
|---|---|---|
| 侵入経路 | CI で使われていた固定されていない悪意ある Trivy 0.69.4 から公開用の認証情報が漏れ、それで公開された(JFrog) | 不明。成果物の注入がビルド時か公開時かも含め調査中。偽リポジトリ Berri-AI/litellm からのビルドが確認されている |
| ソースコード | 検証できるソースが無かった(Chainguard) | GitHub のタグとソースは正常。悪意は wheel とイメージにのみ存在 |
| 起動の仕組み | 3月の件とは別(JFrog) | cook.pth と sitecustomize.py による Python 起動フック |
| 居座り | バックドア、Kubernetes ノードへの拡散(JFrog) | 1時間ごとのリモートコードローダー(ドロップ先は GitHub の domain.txt) |
| 送信先 | models.litellm.cloud | www.litellm.dev |
| 攻撃者 | TeamPCP とされる | 関連を示す証拠は現時点でなし(JFrog、Chainguard) |
| 配布先 | PyPI | PyPI+Docker Hub+GHCR |
共通する教訓は、人気のあるオープンソースの AI 基盤が、攻撃者にとって認証情報の宝庫になっていることだ。メンテナのアカウントを入れ替えても、今回は別の経路で同じ配布先に不正な版が届いた。利用者側は、配布元が安全だという前提を置かず、固定・遅延・隔離で被害を小さくする設計が必要になる。同様に、OS 側の重大な脆弱性への備えとしては、Linux カーネル Copy Fail の緩和策で整理した、パッチ適用と攻撃面の削減の考え方が参考になる。
よくある質問
litellm を使っていないサーバでも影響はありますか。
同じ Python 環境(仮想環境やコンテナ)に 1.107.0 がインストールされていれば、litellm を import していなくても、その環境で Python が起動するたびに悪意あるコードが動く。逆に、1.107.0 が一度も入っていない環境や、1.106.2 以前だけを使っている環境は、報告されている範囲では影響を受けない。依存関係として間接的に入っている場合もあるため、lockfile と site-packages の両方を確認すること。
1.107.0 を入れたが、すぐ削除しました。それでも安全ではないのですか。
安全とは言えない。インストール後に Python が1回でも起動していれば、その時点で窃取が実行された可能性がある。pip でのアンインストールそのものが Python の起動を伴うため、削除作業の中で発火したおそれもある。インストール期間の長さではなく、実行の有無で判断し、実行された可能性が否定できなければ認証情報をローテーションする。
マーカーファイルが無ければ、窃取は成功していないと考えてよいですか。
そうとは言えない。マーカーは、送信が HTTP 200 で成功した後にだけ作られる。送信が失敗した場合、コンテナを作り直した場合、Windows でパスが解決できない場合は、窃取が実行されていてもマーカーが残らない。無いことは感染していない証拠にならず、1.107.0 を実行した事実のほうを基準にする。
PyPI や Docker から削除されたので、もう新しく被害が出ることはありませんか。
新規にインストールされる危険は減ったが、すでに取得した環境は影響を受け続ける。社内のミラーやプロキシ、CI のキャッシュ、コンテナレジストリに 1.107.0 が残っていれば、そこから再び入る可能性がある。また、感染済みのホストでは窃取コードが動き続け、バックドアのローダーは1時間ごとに外部を参照する。現時点ではローダーの参照先が空と報告されているが、感染ホストを放置してよい理由にはならない。
sitecustomize.py が見つかりました。すぐ削除してよいですか。
すぐに削除してはいけない。sitecustomize.py は、ディストリビューションや社内の設定が正規に置いている環境もある。まず中身を確認するか、SHA-256 を公表されている悪意あるハッシュと照合する。site-packages の中で cook.pth と同時に見つかり、内容がハッシュと一致するなら悪意あるファイルである。一致しないものは正規の可能性が高い。
今後、同じ種類の攻撃を防ぐ最も効果的な方法は何ですか。
単独の特効薬はなく、組み合わせになる。優先度が高いのは、依存をバージョンとハッシュで固定すること、Docker をダイジェストで固定すること、新しい版の採用を数日遅らせること、CI の外向き通信を許可リスト方式にすることの4つ。これに加えて、本番の鍵を開発者端末や CI に置かない設計にすると、万一の侵害でも被害が限定される。
この記事に関連する無料ツール(登録不要・その場で結果)
お気軽にご相談ください
お問い合わせ