GitHub.comとGitHub Enterprise Server(GHES)に、認証済みユーザーがgit push一発でバックエンドサーバー上の任意コードを実行できる重大脆弱性「CVE-2026-3854」が見つかった。発見者はクラウドセキュリティ企業Wiz Researchで、AIを活用したリバースエンジニアリングでGitHubの内部git proxy「babeld」のサニタイズ漏れを突き止めた。GitHub.comは2026年3月4日に修正済みだが、GHESは公開時点で88%が未パッチのままだ。

この記事ではGitHubの内部gitパイプラインに発生したRCE脆弱性CVE-2026-3854を解説します。サプライチェーン攻撃全体の対策はサプライチェーンセキュリティ2026|攻撃手法・防御ツール・実践チェックリストをご覧ください。

GitHub Security Response Source: The GitHub Blog — GitHub公式の対応報告。

この記事のポイント

  • git push -oのpush option値にセミコロンを混ぜるだけで、内部のX-Statヘッダーに任意フィールドをインジェクションできる設計欠陥。
  • rails_envcustom_hooks_dirrepo_pre_receive_hooksの3フィールド連鎖で、サンドボックスを無効化したまま任意バイナリを実行できる。
  • GHESは6バージョン系列で個別パッチ提供。3.14.25 / 3.15.20 / 3.16.16 / 3.17.13 / 3.18.8 / 3.19.4以上に即アップグレード推奨。

CVE-2026-3854とは:git push一発でGitHubサーバーが落ちる仕組み

CVE-2026-3854は、GitHubの内部gitパイプラインに存在するコマンドインジェクション系の脆弱性(CWE-77:Improper Neutralization of Special Elements used in a Command)だ。Wiz Researchの発表によれば、CVSS基本スコアは8.7(Critical)で、必要なのは「標準のgitクライアントと、認証済みのpush権限」だけ。ゼロデイ依存もない。

GitHubはユーザーのgit pushを以下のような多段パイプラインで処理している。

サービス 役割
babeld SSHエントリポイント。git proxyとして認証サービスへリクエストを転送
gitauth 認証情報を検証し、ファイルサイズ制限・ブランチルール等のセキュリティポリシーを返却
gitrpcd 内部RPCサーバー。X-Statヘッダーをパースし、自分自身の認証は行わない
pre-receive hook プッシュを受理する前にセキュリティポリシーを強制実行

問題は、babeldgit push -oで渡されたpush option値をサニタイズせずそのままX-Statヘッダーに連結していたことだ。X-Statはセミコロン区切りのkey=value列で、後段のgitrpcdは「同じキーが複数あったら最後の値を採用する(last-write-wins)」というシンプルな実装になっていた。攻撃者がpush optionにセミコロンを混ぜれば、ヘッダーにある正規のセキュリティ設定を後ろから上書きできてしまう。

# 攻撃者の入力例(push option値にセミコロンを混入)
git push -o 'x;large_blob_rejection_enabled=bool:false' origin master

このコマンドがbabeldを通過すると、内部のX-Statヘッダーは概ね次のように構築される。

X-Stat: client_ip=203.0.113.5;rails_env=production;...;custom_field=x;large_blob_rejection_enabled=bool:false

large_blob_rejection_enabled=bool:falseという偽のフィールドが正規フィールドの後ろに挿入され、last-write-winsによってGitHub側のセキュリティ判定が無効化される。これがCVE-2026-3854の核心だ。

サニタイズ漏れの一行が、世界中の数百万リポジトリを脅かしたという意味で、本件は2026年4月のMCP STDIOトランスポート設計欠陥でRCEが発覚した事例と並ぶ「設計レベルの教訓」になる。

攻撃ベクトル:X-Statヘッダーへのセミコロンインジェクション

X-Statヘッダーの内部仕様は、Wizがリバースエンジニアリングで再構築したものによると、概ね以下のような構造である。

X-Stat: <key1>=<value1>;<key2>=<value2>;<key3>=<value3>;...

key例:
- rails_env                      … Railsの実行環境(production/development等)
- custom_hooks_dir               … カスタムフックの探索ディレクトリ
- repo_pre_receive_hooks         … pre-receiveフックの定義(JSON)
- large_blob_rejection_enabled   … 大容量blob拒否フラグ
- max_blob_size                  … blobサイズ上限
- enterprise_mode                … Enterprise機能の有効化フラグ

git push -o key=valueで送られたpush optionは、サーバー側でメタデータの一部として保持され、最終的にX-Statに書き込まれる。push optionの値にセミコロンを混入させれば、攻撃者は以後の任意フィールドを書き換え可能——これが全ての出発点になる。

ここで重要なのは「key=value形式の自由度」だ。後段サービスはX-Statを信頼済みメタデータとみなしているため、認証チェックを行わない。つまり、X-Statに書き込めた瞬間、攻撃者は内部APIに対して特権操作を発行する権利を得る。

下の図は、サニタイズ漏れがどこで発生し、どう連鎖するかを示したものだ。

sequenceDiagram participant U as 攻撃者 participant B as babeld participant A as gitauth participant R as gitrpcd participant H as pre-receive U->>B: git push -o (セミコロン注入payload) Note over B: push optionをサニタイズせずX-Statに連結 B->>A: 認証要求 A-->>B: ポリシー応答 B->>R: RPC + X-Stat (注入済み) Note over R: last-write-winsで偽key=valueを採用 R->>H: hook起動 (rails_env=development) Note over H: サンドボックス無効化 H-->>U: 任意コード実行

push option由来の文字列を信頼境界の内側で再パースする、というアーキテクチャ自体がアンチパターンだったと言える。

3段階のエクスプロイトチェーン:rails_env, custom_hooks_dir, repo_pre_receive_hooks

実際のRCEには、X-Statに注入する3つのフィールドの連鎖が必要だった。

段階 注入フィールド 役割 攻撃者の意図
rails_env=development Railsの実行環境を切替 本番モードのサンドボックスを回避し、直接実行パスを選ばせる
custom_hooks_dir=/tmp/attacker カスタムフック探索ディレクトリ 攻撃者が制御可能なベースディレクトリを指定
repo_pre_receive_hooks=[{"script":"../../usr/bin/id"}] pre-receiveフックのJSON定義 パストラバーサルで任意バイナリを実行

① rails_envでサンドボックスを無効化

pre-receiveバイナリには2つの実行パスがある。productionではサンドボックス内でフックを実行するが、development等の非productionモードでは直接実行される。攻撃者はrails_env=developmentを上書き注入することで、後段のフック実行を非サンドボックスパスに切り替える。

② custom_hooks_dirで探索基点をすり替える

custom_hooks_dirはフックスクリプトを探すベースディレクトリだ。デフォルトはGitHub内部の保護されたパスだが、攻撃者がここを書き換えると、自分が書き込み可能な/tmp配下などに探索パスを向けられる。

③ パストラバーサルで任意バイナリを実行

repo_pre_receive_hooksは通常、JSON配列としてフックスクリプトのリストを定義する。scriptフィールドにパストラバーサル文字列(../../usr/bin/id等)を仕込めば、custom_hooks_dir + scriptの単純連結でファイルシステム上の任意のバイナリへ解決される。フック実行は引数なしでexecされるため、/usr/bin/idや攻撃者が事前に配置したバイナリを直接呼び出せる。

# 概念実装:3フィールド連鎖の同時注入(教育目的・実環境では絶対に試さない)
git push \
  -o "x;rails_env=development" \
  -o "y;custom_hooks_dir=/tmp/attacker" \
  -o 'z;repo_pre_receive_hooks=[{"script":"../../usr/bin/id"}]' \
  origin main

最終的に、攻撃者はgitサービスユーザー権限で任意コマンドを実行できる。gitユーザーはバックエンドノード上のリポジトリ全体に広範な読み書き権限を持っているため、ノード内の他テナントのリポジトリにも到達可能だった。これが本件を「クロステナント攻撃」と呼ばせた理由だ。

GitHub.com vs GitHub Enterprise Serverへの影響範囲

最初の発見時点では、エクスプロイトはGHES 3.19.1上では成立する一方、GitHub.com上ではcustom hooksパスに到達できなかったという。Wizの分析チームはここで足を止めず、X-Statenterprise modeを切り替えるboolean flagが含まれていることをリバースエンジニアリングで特定し、これをセットすることでGitHub.com側でもcustom hooksパスを開通させた。

環境 影響範囲 結果
GitHub Enterprise Server (GHES) ≤3.19.1 サーバー全体 フルテイクオーバー(全リポジトリ・全管理者権限)
GitHub.com 共有ストレージノード 同一ノード上の数百万リポジトリにアクセス可能
GitHub.com(クロステナント) 異なる組織のリポジトリ 同一ノードに同居する他組織リポジトリも露出

GitHubは公式ブログで「フォレンジック調査の結果、脆弱コードパスへの到達はWizのテスト活動のみで、他ユーザー・アカウントによるトリガーは確認されなかった」と述べ、「顧客データへのアクセス・改ざん・流出は発生していない」と結論付けている。とはいえ、GHES側では88%のインスタンスが公開時点で未パッチというデータがWizから示されており、ここからは管理者の責任領域だ。

影響を受けるGHESバージョンと修正バージョン一覧

GitHubは公式アドバイザリで、サポート対象の全バージョン系列に個別パッチをリリースした。下表は公開情報をまとめたものだ。

バージョン系列 脆弱バージョン 修正バージョン
3.14系 3.14.24以下 3.14.25以上
3.15系 3.15.19以下 3.15.20以上
3.16系 3.16.15以下 3.16.16以上
3.17系 3.17.12以下 3.17.13以上
3.18系 3.18.7以下 3.18.8以上
3.19系 3.19.3以下 3.19.4以上
3.20系 3.20.0以上(最初から修正済み)

GHESを運用している場合、まずは現在のバージョンを確認する。

# GHES管理シェルからバージョンを確認
ghe-version
# 出力例:
# GitHub Enterprise Server 3.18.5

3.18.5なら3.18.8以上へ、3.19.1なら3.19.4以上へアップグレードする必要がある。本記事の執筆時点でGitHub.com側はすでに修正済みなので、自社で運用するGHESを優先的に対処すべき場面だ。

なお、本件と並行して2026年4月時点ではGitHub Actions経由のサプライチェーン攻撃や、依存パッケージマネージャ経由のRCEも複数報告されている。CIサイドの全体像はRenovate・DependabotとGitHub Actionsを狙うサプライチェーン攻撃の防御策で整理しているので、合わせて読むと効果が高い。

AI支援リバースエンジニアリング:WizのIDA MCP活用

本件のもう一つの注目点は、Wizが採用した「AIエージェント+IDA MCPによるリバースエンジニアリング」という研究手法だ。Wiz自身が公開ブログで「手作業では到達できない速度でコンパイル済みバイナリを解析し、内部プロトコルを再構築した」と述べている。

具体的には、

  • babeldgitrpcd等のクローズドな内部バイナリをIDA Pro上で解析
  • IDA MCPサーバーをClaude/AIエージェントに接続し、関数の意図・データフロー・呼び出し関係をAIに整理させる
  • 注入可能フィールドの候補を機械的に列挙し、PoCに繋げる

というワークフローだ。AIに脆弱性を直接見つけさせるというより、「人間アナリストの認知負荷を10倍下げ、調査範囲を10倍広げる」用途と理解すると分かりやすい。MCPサーバーを使った開発者体験そのものは、Anthropic MCP STDIOの設計欠陥でRCEに至った事例でも論点化しており、攻撃側・防衛側の両方でMCP生態系が急速に道具化している。

# Wiz方式に近い、AI+IDA MCPによる関数解析の擬似コード
from ida_mcp import IdaClient
from anthropic import Anthropic

ida = IdaClient(host="localhost", port=9090)
ai = Anthropic()

# babeldバイナリ内のpush option処理関数を抽出
funcs = ida.list_functions(name_pattern="*push_option*")
for f in funcs:
    pseudo = ida.decompile(f.address)
    resp = ai.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": (
                "次のC擬似コードでユーザー入力がサニタイズされているか、"
                "セミコロン等の特殊文字をどう扱うか、表で整理して。\n\n"
                f"```\n{pseudo}\n```"
            ),
        }],
    )
    print(f.name, resp.content[0].text)

実装そのものはWizが公開していないため、これはあくまで概念例だが、AIが「サニタイズの有無」を関数横断で機械的にレビューするだけでも、設計レベルの脆弱性発見コストは桁違いに下がる。CVE-2026-3854が「2026年中に複数の同種脆弱性が連続発見される」シリーズの1本になる可能性は高い。

検知と対策:監査ログから攻撃を特定する

公開時点で既に修正パッチが配布されているが、過去に攻撃を試みられていないかの遡及調査は別問題だ。GitHubは公式ブログで、GHES管理者に対して/var/log/github-audit.logの監査ログをチェックすることを推奨している。

検知ポイントはシンプルで、push optionにセミコロン;を含むpush操作を洗い出すだけだ。

# GHES監査ログから、push optionにセミコロンが入った操作を抽出
sudo grep -E 'push_options=[^"]*;' /var/log/github-audit.log \
  | awk '{print $1, $2, $NF}' \
  | sort -u

正規のpush optionにセミコロンが入ることは通常ないため、ヒットすればほぼ確実に攻撃試行と判定できる。検出した場合は、actorrepositorysource_ipを確定させ、フォレンジック調査に移行する。

優先順位の付け方は次の通りだ。

優先度 対応 期限の目安
🔴 緊急 GHESを修正バージョンへアップグレード 即時〜24時間以内
🟠 高 /var/log/github-audit.logを遡及調査(最低90日) 7日以内
🟠 高 カスタムフックディレクトリの権限・所有者を再点検 7日以内
🟡 中 push権限を持つユーザー・サービスアカウントを棚卸し 30日以内
🟡 中 不要なpush optionを利用するCI/CDジョブの監査 30日以内
🟢 継続 WAF/SIEMでX-Stat含むHTTPヘッダーをモニタリング 継続運用

社内CIのなかには、デプロイメタデータをpush optionで送る設計も少なくない。今後はサーバー側だけでなく、自社CIから出ていくpush optionに不正な特殊文字が混じらないかもチェック観点に加えるべきだ。GitHub上のコメント・PR本文を経由した攻撃ベクトルも別軸で広がっており、GitHub Issueコメントを使ったAIエージェントへのプロンプトインジェクション攻撃と合わせて全体像を把握したい。

まとめ:信頼境界を「文字列パース」で跨いではいけない

CVE-2026-3854から得られる教訓は、技術的には地味だが普遍的だ。

  • 信頼境界を越える文字列は必ずサニタイズし、構造化された型で運ぶ(key=valueの平文連結ではなくJSONや長さプレフィクス付きフォーマット等)。
  • last-write-winsはユーザー入力を含むメタデータには使わない。重複キーは即エラーが安全側。
  • マルチサービス間で「同じヘッダー」を共有する場合、各サービスでの再認証・再検証は省略しない
  • セキュリティ研究にAIを組み込んだ攻撃者は、設計欠陥を桁違いの速度で見つける。同じ手法を防御側も導入すべき。

GitHubはGitHub.comを6時間で塞ぎ、GHESにも全系列のパッチを並行リリースした。残るボトルネックはGHES運用者側だ。88%という未パッチ率が示すのは、「クリティカルな修正リリースであっても、運用者がアップグレードを実行しない限り脆弱性は残り続ける」という当たり前の事実だ。本記事を読んだ方は、自組織のGHESバージョンと監査ログを今すぐ確認してほしい。

参照ソース