GPU上でLLMの推論サーバーを管理するOSS、GPUStackの認証回避CVE-2026-102895が、2026年11月9日にCISAの悪用確認済み脆弱性カタログ(KEV)へ追加されました。2.2.0より前の版では、認証情報を持たない外部からの要求が組み込みの管理者として扱われることがあります。この記事では、管理者になってしまう2つの経路と原因コード、手元の版と露出を調べるコマンド、Dockerで2.3.0へ上げる手順、すぐ更新できない環境の暫定策を順に整理します。本記事の記述は11月10日時点で公開されている情報にもとづくものです。
まとめ:GPUStackは2.2.0で止めず2.3.0へ、更新前に露出と侵害の確認
CVE-2026-102895そのものは2.2.0で直っています。ただ、2.2系には別の認証回避と認可の欠陥が残り、2.3.0で多数が修正されました。これから更新するなら、11月10日時点の最新安定版である2.3.0を選んでください。
更新より先にやることもあります。2.1系の管理画面やAPIをインターネットへ出していた環境は、すでに侵入されている前提で、ユーザー・APIキー・推論バックエンドを洗い出します。米国連邦機関向けのKEVの是正期限は11月12日で、民間でも同じ速さで片づける案件です。手が回らない場合は、脆弱性診断・セキュリティ診断で公開範囲と設定の確認を受け付けています。
CVE-2026-102895で未認証の要求が管理者になる2つの経路と原因コード
GitHubのアドバイザリGHSA-gj9m-f8ph-pmhrは、原因を「送信元のネットワーク情報を認証の根拠にしたこと」と説明しています。仕組みはコードを見ると短く理解できます。
ループバック判定で管理者を割り当てていたauth.pyの3行と修正差分
修正コミット53f42a6は、gpustack/api/auth.pyから次の3行を消しただけの変更です。トークンでユーザーが決まらず、送信元が127.0.0.1と判定され、force_auth_localhostが偽なら、最初に見つかった管理者ユーザーを割り当てていました。
# 2.1系の get_current_user(修正で削除された部分)
if user is None and client_ip_getter(request=request) == "127.0.0.1":
if not server_config.force_auth_localhost:
user = await User.first_by_field(session, "is_admin", True)
force_auth_localhostの既定値はFalseでした。つまり初期設定のままなら、送信元さえ127.0.0.1に見えれば認証なしで管理者になります。
同一ホストのリバースプロキシでTCPピアが127.0.0.1になる構成
1つ目の経路は、細工を必要としません。nginxなどを同じホストに置き、127.0.0.1のGPUStackへ転送している構成では、GPUStackから見た接続元が常にプロキシ自身、つまり127.0.0.1になります。外部の誰がアクセスしても管理者扱いです。
TLS終端のためにプロキシを前段へ置くのはよくある構成です。プロキシの役割と置き方はリバースプロキシの仕組みと導入判断で解説しています。今回は、その定番構成がそのまま穴になった点が厄介です。
埋め込みゲートウェイがX-GPUStack-Real-IPの値を信じる条件
2つ目の経路はヘッダーの偽装です。2.1.1のclient_ip_getterは、ゲートウェイを埋め込み(embedded)で動かしているとき、要求に付いたX-GPUStack-Real-IPヘッダーの値をそのまま送信元として返していました。
# 2.1.1 の client_ip_getter
def client_ip_getter(request: Request) -> str:
if request.app.state.server_config.gateway_mode == GatewayModeEnum.embedded:
return request.headers.get("X-GPUStack-Real-IP", "")
else:
return request.client.host
攻撃者がこのヘッダーに127.0.0.1を入れれば、どこから接続しても条件を満たします。2.3.0の同じ関数は、ゲートウェイのトークンを確かめた要求に限ってプロキシ由来のヘッダーを読む形に変わっています。
影響版・修正版とCISA KEV追加で11月12日が是正期限となった経緯
修正自体は早く出ていました。修正コミットは2026年5月19日、2.2.0のリリースは6月18日、GHSAの公開は7月31日です。一方でNVDへの登録は11月4日でした。NVDのデータに頼る脆弱性スキャナでは、11月まで検出されなかった可能性があります。
2.2.0未満が対象で2.1.1環境のリバースシェル悪用が確認済み
影響を受けるのはPyPIのgpustackパッケージ2.2.0未満で、Dockerイメージも同じ版で判断します。CVSS 3.1の基本値は9.8で、ネットワーク越しに認証も利用者の操作も要りません。
NVDの記載では、2.1.1の環境で実際に悪用されています。管理者としてPOST /v2/inference-backends/from-yamlを呼び、リバースシェルを仕込んだ独自の推論バックエンドを登録する手口です。ワーカーやクラウド事業者の秘密情報の読み出し、永続的な管理者APIキーの作成も影響として挙がっています。CISAのKEVは11月9日に本件を追加し、是正期限を11月12日としました。期限の決まり方はBOD 26-04の是正期限の仕組みで整理しています。
2.2系に残るSAML認証回避など2.3.0までに直った別のアドバイザリ
2.2.0へ上げれば本件は塞がります。それでも2.2.0を勧めないのは、同じ時期に次の欠陥が公表されているためです。
| 識別子 | 内容 | 影響版 | 修正版 |
|---|---|---|---|
| CVE-2026-102895 | ループバック判定による認証回避(9.8) | 2.2.0未満 | 2.2.0 |
| CVE-2026-102894 | SAMLコールバックの署名未検証(9.1) | 0.7.1以上2.2.2未満 | 2.2.2 |
| CVE-2026-103002 | Kubernetes API中継認可漏れ(8.8) | 2.2.0以上2.2.2未満 | 2.2.2 |
| GHSA 8件(10月26日公開) | 認証情報ファイル権限・ワーカー管理API認可など | 2.2.3以前を含む | 2.3.0 |
SAMLの欠陥は、SAMLを設定していない環境でも、既存の管理者を名乗るセッションを発行できたというものです。Kubernetes APIプロキシの欠陥は、一般ユーザーがワーカーの権限でクラスタを操作できたというものです。残る8件はリポジトリのセキュリティ一覧と2.3.0のリリースノートに並んでいます。
手元のGPUStackで版番号と未認証で管理者になるかを調べるコマンド
調べる項目は2つです。動いている版と、外から認証なしで管理者の情報が返るかどうかです。どちらも読み取りのみで、サービスを止めません。
gpustack versionとDockerイメージのタグで動いている版を確認
# Dockerで動かしている場合(コンテナ名は公式手順の既定値 gpustack)
docker ps --filter name=gpustack
docker exec gpustack gpustack version
# pipで入れた場合
pip show gpustack
1行目のIMAGE列にlatestと出ていても、取得した時期によって中身の版は違います。2行目のgpustack versionの結果を正としてください。ワーカーを別ホストで動かしている場合は、そちらでも同じ確認をします。
認証情報なしのcurlで自分のユーザー情報APIが401を返すかの確認
2.1.1では、/v2/users/meは認証済みユーザー向けのAPIです。認証情報を付けずに、利用者が実際に使うURLから呼び出します。自社で管理するサーバー以外には実行しないでください。
# 経路1:プロキシ経由の通常のアクセス
curl -s -o /dev/null -w "%{http_code}\n" https://gpustack.example.com/v2/users/me
# 経路2:送信元ヘッダーを付けたアクセス
curl -s -o /dev/null -w "%{http_code}\n" -H "X-GPUStack-Real-IP: 127.0.0.1" https://gpustack.example.com/v2/users/me
どちらも401なら、少なくともこの2経路での認証回避は成立していません。200が返った場合は回避が成立しており、すでに誰かが同じことをした可能性があります。その環境は後述の侵害確認へ進んでください。
Docker環境をバックアップ後に2.3.0へ更新しサーバーとワーカーを戻す手順
公式のDocker更新手順は、v2.0以降を対象に「イメージを取得し、同じ引数とボリュームでコンテナを作り直す」流れを示しています。順番はサーバーが先、ワーカーが後です。
埋め込みPostgreSQLのデータを退避してからサーバーを入れ替える順番
公式手順は、更新前にサーバーを止めてデータベースを退避するよう強く勧めています。既定構成では/var/lib/gpustack/postgresql/dataが埋め込みPostgreSQLのデータです。次の例は、公式の起動例どおりgpustack-dataボリュームを使っている前提です。
docker stop gpustack
# ボリューム全体をtarで退避(Docker一般の方法)
docker run --rm -v gpustack-data:/data -v "$PWD":/backup alpine \
tar czf /backup/gpustack-data-20261110.tgz -C /data .
docker pull gpustack/gpustack:v2.3.0
docker rm gpustack
docker run -d --name gpustack --restart unless-stopped \
-p 80:80 --volume gpustack-data:/var/lib/gpustack \
gpustack/gpustack:v2.3.0
docker exec gpustack gpustack version
起動引数は、元のコンテナと同じものを必ず付け直します。11月9日には2.3.1rc1も出ていますが、公式手順はrc版への更新を避けるよう明記しています。安定版の2.3.0に固定してください。
ワーカーの更新とforce_auth_localhostが無効になった後の設定整理
サーバーが新しい版で動き出したら、各クラスタのワーカーを更新します。2.2.0でサーバーとワーカーの版の不一致を検知する仕組みが入ったため、ワーカーを古いまま残さないでください。
2.3.0のCLIリファレンスでは、--force-auth-localhostは「互換のために残した何もしない設定」です。ローカルホストからの要求も常に認証されます。設定ファイルに残っていても起動は止まりませんが、将来の版で削除予定なので消しておきます。リバースプロキシの後ろで動かす場合は、同じ文書にある--trusted-hostsで信頼するホスト名を指定してください。未指定のまま--server-external-urlも無いと、全ホストを信頼する*になります。
すぐ更新できない2.1系で入れる暫定策とリバースプロキシの設定例
アドバイザリが示す暫定策は3つです。ローカルホストにも認証を要求する設定、プロキシでのヘッダー除去、APIを信頼できないネットワークから切り離すことです。どれも更新までのつなぎで、更新の代わりにはなりません。
force_auth_localhostをtrueにして起動し直す設定と環境変数
2.1系のCLIリファレンスには、多くの起動引数をGPUSTACK_接頭辞の大文字の環境変数でも指定できると書かれています。Dockerなら次のように付けて作り直します。
docker run -d --name gpustack --restart unless-stopped \
-e GPUSTACK_FORCE_AUTH_LOCALHOST=true \
-p 80:80 --volume gpustack-data:/var/lib/gpustack \
gpustack/gpustack:v2.1.1
設定ファイルを使っているならforce_auth_localhost: trueを書き足します。反映できたかは、前章のcurlが2本とも401になるかで判定します。同一ホストのプロキシ構成では、この設定が経路1を塞ぐ唯一の手段です。ヘッダー除去だけでは足りません。
nginxで偽装ヘッダーを転送前に消すproxy_set_headerの書き方
経路2に対しては、外部から届いたX-GPUStack-Real-IPをエッジで捨てます。nginxの公式文書によると、proxy_set_headerの値を空文字列にしたヘッダーは転送先へ渡されません。
# GPUStackを -p 127.0.0.1:8080:80 で公開し、nginxを前段に置く例
location / {
proxy_set_header X-GPUStack-Real-IP "";
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080;
}
この設定だけでは、プロキシ経由の要求がGPUStackから127.0.0.1に見える状態は変わりません。前節のforce_auth_localhostと必ず組み合わせてください。管理画面を社内網やVPNの内側へ移せるなら、それが最も確実な切り離しです。
判断:侵害を疑うべきGPUStack環境と更新だけで済ませてよい条件
更新だけで終えてよいのは、2.1系の管理画面とAPIが社内網やVPNの内側にしかなく、前章のcurlを外部から打てなかった環境です。それ以外は、更新と並行して侵害の確認を行います。
管理画面を外部公開していた2.1系は更新前に侵害の痕跡を調べる判断
同一ホストのプロキシで外部公開していた2.1系は、curlの結果に関係なく、外部の誰もが管理者だった期間があったと見なします。悪用の手口が公開済みで、KEVにも載ったからです。痕跡を消される前に確認したいので、更新の前にコンテナのログと管理画面の状態を控えておきます。
見送ってよい対応もはっきりさせます。WAFでX-GPUStack-Real-IPを含む要求だけを止める対策は、経路1を塞げないので採用しません。同じGPU基盤の露出が狙われた例としては、Rayクラスタを狙ったShadowRay 2.0があり、関連記事で扱っています。
推論バックエンド・APIキー・クラウド認証情報の棚卸しと失効の順番
確認は、攻撃者が残しそうなものから順に行います。
- 推論バックエンドの一覧に、身に覚えのない独自バックエンドやYAML登録が無いかを見る
- ユーザー一覧に、作った覚えのない管理者が無いかを見る
- APIキーを全件失効させ、必要なものだけ発行し直す
- GPUStackに登録したクラウド事業者の認証情報とワーカーのトークンを、発行元で無効化して入れ替える
- ワーカーホストで不審なプロセスと外向きの通信を調べる
3と4は、痕跡が見つからなくても実施します。アクセスの記録が残っていない以上、読み出されていないとは言えないからです。キーの失効と入れ替えの設計はAPIキー管理の発行・失効・ローテーションが参考になります。推論の入出力に個人情報が含まれていた場合は、個人情報保護委員会への報告義務の該当性も確認してください。
よくある質問
GPUStackの脆弱性CVE-2026-102895について、運用の現場で迷いやすい点に答えます。
2.2.0に上げていれば対応は完了ですか?
CVE-2026-102895に限れば2.2.0で修正されています。ただ、SAMLコールバックの署名未検証(CVE-2026-102894)とKubernetes APIプロキシの認可漏れ(CVE-2026-103002)は2.2.2まで残り、ワーカー管理APIの認可や認証情報ファイルの権限など8件は2.3.0で直りました。11月10日時点では2.3.0への更新を完了の基準にしてください。
リバースプロキシを使っていなくても影響はありますか?
あります。ゲートウェイを埋め込みで動かしている2.1系では、X-GPUStack-Real-IPヘッダーに127.0.0.1を入れるだけで条件を満たします。プロキシの有無ではなく、管理画面とAPIに外部から届くかどうかで判断してください。curlの2本目が401を返すかで確かめられます。
KEVの是正期限11月12日は民間企業にも適用されますか?
KEVの是正期限は、BOD 26-04にもとづき米国の連邦政府機関に課されるものです。日本の民間企業に法的な拘束力はありません。それでも、KEVへの追加は実際の攻撃が確認されたことを意味します。インターネットから到達できるGPUStackは、同じ期限を目安に更新か切り離しを終えてください。
ワーカーだけ古い版のままでも大丈夫ですか?
CVE-2026-102895の判定処理はサーバー側のコードです。ただし2.3.0で直った欠陥には、ワーカー登録や認証情報ファイルの権限に関わるものも含まれます。公式手順もサーバーの後にワーカーを更新する順番を示しているので、両方を2.3.0にそろえてください。
悪用されたかどうかをログで判定できますか?
アドバイザリとNVDに、侵害を判定する具体的な方法は書かれていません。確認の手がかりは、身に覚えのない推論バックエンドや管理者ユーザー、APIキーの有無です。プロキシのアクセスログにX-GPUStack-Real-IPが記録されていれば参考になりますが、既定のログ形式では残らないことが多いため、無いことを安全の根拠にはできません。
関連記事
- vLLMとは?PagedAttentionの仕組み・使い方とOllama・TensorRT-LLMとの違い・0.32.0の脆弱性対応:GPUStackが裏で起動する推論エンジンの仕組みと脆弱性対応です。
- ShadowRay 2.0とは?Rayクラスタを狙う自己増殖ボットネットと露出面の閉じ方:GPU基盤の管理面が狙われた別の事例と、露出の閉じ方を扱います。
- API不正アクセス対策の実装手順:JPCERT注意喚起に沿うレート制限・認可・トークン失効とアプリ解析・SSRF対策:送信元ではなくトークンで認可する設計の基本をまとめています。