
GitLab CE 19.4.1のGitaly fetch SSRFに関するPoCおよびラボ: GitHub/Bitbucketインポーターがresolved_addressのピン留めをスキップするため、認証済みユーザーがDNSを再バインドして内部ホストを取得できる。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitaly-fetch-ssrf
GitLab CE 19.4.1 - GitLab Inc.
GitHub Enterprise / Bitbucket Server インポート(およびそのフラグが有効な場合の Direct Transfer)は Projects::ImportService#get_resolved_address をスキップする。Repository#fetch_as_mirror は resolved_address: "" をデフォルトとする。Gitaly の FetchRemote / CreateRepositoryFromURL はクローン先ホスト名を再解決する。UrlBlocker はすでにパブリック A レコードを許可していた。リバインドすれば、git-HTTP オブジェクトがインポート先プロジェクトに到達する。
GitHub Enterprise インポートを実行できるサインイン済みユーザーは、Gitaly に UrlBlocker がピン留めした IP とはもはや異なるホストを git-fetch させることができる。
| ID | CVE 未採番 |
| CWE | CWE-918, CWE-367 |
| CVSS | High: 7.7 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N |
| 製品 | GitLab CE |
| 影響を受けるバージョン | 19.4.1-ce.0(v19.4.1-ee ツリー上の 26212baacadb) |
| 認証 | 認証済みユーザー、GitHub Enterprise / Bitbucket Server インポーター |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
GitHub Enterprise インポートを、攻撃者が制御するホスト名に向ける。Rails の UrlBlocker.validate! はパブリック A レコードを許可する。ParallelImporter.imports_repository? が true のため、ImportService はピン留めしない。RepositoryImporter#fetch_as_mirror は空の resolved_address で Gitaly を呼び出す。Gitaly はその名前が現在指す先を git HTTP でフェッチする。インポートされたリポジトリにはそのオブジェクトが含まれる。
オールインワンの omnibus では、これはワーカーのループバックである。http://127.0.0.1 を直接指定したクローンホスト名はフェイルクローズする。URL によるリポジトリインポートはすでにピン留めしている。Direct Transfer には bulk_import_enabled が必要(セルフマネージドのデフォルトは false)。
同じ製品の類似の残存問題: Gitea HTTPS インポートのピン留め欠落、webhook 0.0.0.0/8。
19.4.1 のパッチノートを読み、次にインポーターを読んだ。Projects::ImportService#get_resolved_address はホスト名を検証済み IP に書き換え、それを Gitaly に渡す。GitHub と Bitbucket Server の並列インポーターは imports_repository? を設定し、その経路をスキップする。
project.repository.fetch_as_mirror(project.unsafe_import_url, refmap: refmap, forced: true)
fetch_as_mirror は resolved_address: "" をデフォルトとする。Direct Transfer の repository_pipeline.rb は validate! を呼び出しながら戻り値を破棄している。
私は素の gitlab/gitlab-ce:19.4.1-ce.0 を立ち上げた。CE の import_sources は空で始まるため、ラボでは GitHub を有効化する。POST /api/v4/import/github を github_hostname=http://ghe.lab:18080 で送信する。GitLab の netns 内のキャッチャーは偽の GHE JSON と git http-backend を話す。シードリポジトリのブロブ WITNESS は GITLAB-GITALY-FETCH-SSRF-WITNESS である。
GET /api/v4/projects/1/repository/files/WITNESS/raw?ref=master が witness を返した。Gitaly ログ: root/ghe-ssrf に対する FetchRemote の grpc.code=OK。キャッチャー: git/2.55 の info/refs と git-upload-pack。ネガティブテストの github_hostname=http://127.0.0.1:18080 は 400 Invalid URL となる。
すでに記録済みの回り道: シード CveLabRoot9! は WeakPasswords で拒否される(root 部分文字列)。19.4 ではそのチェックを 64 文字で回避する。Faraday はキャッチャーではないパブリック IP に接続できないため、ハーネスは 1.1.1.1:18080 を REDIRECT しつつ UrlBlocker にはパブリック A を見せ続ける。git-HTTP は GET /repos/org/repo が DNS を切り替える前に勝つことがある。GitLab はインポート途中で 502 を返し、その後ファイルはそこにある。リバースシェル。芝居。オラクルはインポートされたブロブと Gitaly の FetchRemote である。
cd lab
./run.sh
対象は http://127.0.0.1:18410 のみ。GitLab CE は数 GB の RAM を要求する。許可されたラボのみ。
negative-loopback status=400 Invalid URL
witness-import status=201 import_status=scheduled
file-final http=200 body='GITLAB-GITALY-FETCH-SSRF-WITNESS'
SUCCESS GITLAB-GITALY-FETCH-SSRF imported-witness git-fetch-no-pin GITLAB-GITALY-FETCH-SSRF-WITNESS
def import_repository
project.ensure_repository
refmap = Gitlab::GithubImport.refmap
project.repository.fetch_as_mirror(project.unsafe_import_url, refmap: refmap, forced: true)
URL によるリポジトリインポートがすでに行っているように、get_resolved_address を fetch_as_mirror に渡すこと。Direct Transfer でも UrlBlocker.validate! を破棄しないこと。
repository_importer.rb · repository.rb · import_service.rbGNU Affero GPL v3.0。LICENSE を参照。ループバックラボのみ。無保証。