
GitLab CEのWebhook SSRF(0.0.0.0/8のthis-network範囲を経由)の概念実証およびラボ。Maintainerがループバックに到達し、フックログからレスポンスを読み取ることができる。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-webhook-0net-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitlab::HTTP_V2::UrlBlocker は 0.0.0.0、::、および 127.0.0.0/8 を正確にブロックする。しかし 0.0.0.0/8 の残りの範囲はブロックしない。Linux はこの範囲を「this network」として扱い、0.0.0.1 をローカルスタックに配送する。プロジェクトの Maintainer は、allow_local_requests_from_web_hooks_and_services がオフの状態でも http://0.0.0.1:<port>/ に webhook を作成できる。GitLab はレスポンスの 8 KB を hook ログに保存する。
自身のプロジェクトの Maintainer は、GitLab に 0.0.0.1 経由で loopback へ POST させ、そのボディを hook ログから読み戻すことができる。
| ID | CVE 未採番 |
| CWE | CWE-918 |
| CVSS | High: 8.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N |
| 製品 | GitLab CE |
| 影響を受けるバージョン | 19.4.1-ce.0 (v19.4.1-ee ツリー上の 26212baacadb) |
| 認証 | 認証済み Maintainer (admin_web_hook) |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
POST /api/v4/projects/:id/hooks を url=http://0.0.0.1:<port>/ で実行する。トリガーまたはテストを行う。hook events API から response_body を読み取る。オールインワンの omnibus では、Sidekiq は Puma、Workhorse、および各種 exporter とコンテナの netns を共有している。自身のプロジェクトの Maintainer がこれらを読むべきではない。
http://127.0.0.1/ と http://169.254.169.254/ はブロックされたままである。これは link-local 経由の IMDS ではない。this-network の穴である。
同じ製品の類似の残存問題: Gitea HTTPS import pin drop、Gitaly fetch omits resolved_address。
19.4.1 の SSRF 修正後に url_blocker.rb を読んだ。validate_localhost は "::" と "0.0.0.0" の完全一致、および Socket.ip_address_list である。validate_loopback は ipv4_loopback? (127.0.0.0/8) と ipv6 loopback である。spec は 127.0.0.1 の octal/hex エイリアスをカバーしている。しかし 0.0.0.1 については一切言及されていない。
webhook がボディのシンクである: WebHookService が POST し、8 KB を保存する。Maintainer は admin_web_hook を持つ。
素の gitlab/gitlab-ce:19.4.1-ce.0 を立ち上げた。ローカルリクエストはオフ。GitLab の netns 内の 0.0.0.1:18080 にキャッチャーを配置。hook 作成 http://127.0.0.1:18080/ と http://169.254.169.254/ : 422 Invalid url given。hook 作成 http://0.0.0.1:18080/ : 201。push イベントのテスト : 201。hook events の response_body に GITLAB-WEBHOOK-0NET-SSRF-WITNESS が含まれる。
すでに記録済みの回り道: Accept: application/json を付けた /users/sign_in は 404。root を含む seed パスワードは WeakPasswords。0.0.0.1 を lo に追加すると Socket.ip_address_list に載るため、validate_localhost が「ポジティブ」を 422 にする。IP_FREEBIND と ip route add local 0.0.0.1/32 dev lo はアドレスリストに参加せずにバインドする。compose のパスワード内の $ は $$ が必要。リバースシェル。芝居。オラクルは 0.0.0.1 で 201、127.0.0.1 で 422、ログに witness である。
cd lab
./run.sh
http://127.0.0.1:18420 のみを対象とすること。GitLab CE は数 GB の RAM を要求する。認可されたラボのみ。
negative-a 127.0.0.1 blocked
negative-b link-local blocked
positive-hook id=1
SUCCESS GITLAB-WEBHOOK-0NET-SSRF webhook response_body from 0.0.0.1:18080 hook_id=1 GITLAB-WEBHOOK-0NET-SSRF-WITNESS
def validate_localhost(addrs_info)
local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address))
return if (local_ips & addrs_info.map(&:ip_address)).empty?
raise BlockedUrlError, "Requests to localhost are not allowed"
end
def validate_loopback(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? }
raise BlockedUrlError, "Requests to loopback addresses are not allowed"
end
127.0.0.0/8 が拒否されるのと同じ方法で 0.0.0.0/8 (および ::ffff:0.0.0.0/104) を拒否すること。
url_blocker.rb · web_hook_service.rb · project_hooks.rbGNU Affero GPL v3.0。LICENSE を参照。loopback ラボのみ。無保証。