
GitLab - 高 - Gitea HTTPSインポートSSRF - UrlBlockerピンドロップ
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitea-import-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitea インポートは UrlBlocker.validate! を一度呼び出した後、https の場合には IP に固定した URI を捨て、元のホスト名をそのまま Faraday に渡す。Octokit の UrlValidation は再度検証するが、env[:url] を書き換えない。これらのルックアップの間に名前を再バインドすると、GitLab は Gitea であるかのようにループバックへ GET する。リポジトリ一覧の JSON は GET /import/gitea/status.json で返ってくる。HTTP インポートはピンを維持する。直接の https://127.0.0.1 はブロックされる。
個人ネームスペースで Gitea インポートを開けるサインイン済みユーザーは、GitLab に自身がループバックへ再バインドしたホストを取得させることができ、そのレスポンスボディを入手できる。
| 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) |
| 認証 | 認証済みユーザー、Gitea インポート、個人ネームスペース |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
Gitea の Host URL を https://<攻撃者が制御する名前>/ に設定する。UrlBlocker は最初のパブリック A レコードを許可する。HTTPS の client_options はそのホスト名を保持する。127.0.0.1 へ再バインドする。Faraday は GitLab ワーカーのループバック上でキャッチャーへ GET する。status.json の provider_repos はキャッチャーが返した内容そのものになる。
オールインワンの omnibus では、それは Workhorse、Puma、エクスポーター、およびワーカー上で待ち受けているその他すべてである。ホストの RCE ではない。未認証でもない。HTTP の Host URL は固定された IP のままである。
同じ製品の類似の残存問題: Gitaly fetch は resolved_address を省略、webhook 0.0.0.0/8。
まず 2026 年 9 月 23 日の 19.4.1 / 19.3.3 / 19.2.7 のパッチノートを読み、次に v19.4.1-ee ツリーを読んだ。CI 正規表現 RCE、MR diff XSS、Workhorse の file.path 読み取りはこのピンでは修正済みである。インポート SSRF は未完了だった。
Import::GiteaController#client_options が残存箇所である:
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
HTTP は verified_url (IP) を使う。HTTPS は provider_url (ユーザーが入力した名前) を使う。LegacyGithubImport::Client はそのホストから Octokit の api_endpoint を構築する。Gitlab::Octokit::UrlValidation は validate! を呼び出した後、書き換えなしで @app.call(env) を実行する。
私は素の gitlab/gitlab-ce:19.4.1-ce.0 を立ち上げた。ローカルリクエストは無効。Faraday の ssl.verify が true なので、ラボ CA を /etc/gitlab/trusted-certs に配置した。gitea.lab の DNS: 1〜2 回目のルックアップは 1.1.1.1 を返し、以降のルックアップは 127.0.0.1 を返す。キャッチャーは GitLab の netns を共有し :443 で待ち受ける。GET /import/gitea/status.json は GITLAB-GITEA-IMPORT-SSRF-WITNESS という名前の provider_repos を返した。キャッチャーのログ: GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab。
直接の https://127.0.0.1/ : 「Specified URL cannot be used」。HTTP の http://gitea.lab/ はピンのままで、witness を返さなかった。
すでに記録済みの誤った道: Accept: application/json を付けた /users/sign_in は 404 (HTML は 200)。シードパスワード CveLabRoot9! は root を含むため拒否される。CA のバインドマウントを ro にすると、reconfigure がファイルをリンクした後に EROFS になる。ルックアップ 2 回目で最初の 2 回→ループバックとすると、Faraday が接続する前に UrlValidation がブロックする。ペアモード (奇数回はパブリック、偶数回はループバック) がうまくいった。リバースシェル。芝居。オラクルは status.json 内の witness とキャッチャーの peer である。
cd lab
./run.sh
対象は http://127.0.0.1:18400 のみ。GitLab CE は数 GB の RAM を必要とする。初回起動は遅い。許可されたラボのみ。
status.json status=200 ... provider_repos ... GITLAB-GITEA-IMPORT-SSRF-WITNESS
catcher GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab status=200 witness=True
negative-a https://127.0.0.1 blocked
negative-b http://gitea.lab no-witness
SUCCESS GITLAB-GITEA-IMPORT-SSRF status.json mode=pairs http=200 GITLAB-GITEA-IMPORT-SSRF-WITNESS
def client_options
verified_url, provider_hostname = verify_blocked_uri
{
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
api_version: 'v1',
hostname: provider_hostname
}
end
HTTP がすでに行っているのと同じように、https でも verified_url.to_s を使うこと。Octokit の UrlValidation で env[:url] を固定 IP に書き換えること。
gitea_controller.rb · legacy_github_import/client.rb · octokit/url_validation.rbGNU Affero GPL v3.0。LICENSE を参照。ループバックラボのみ。無保証。