
إثبات مفهوم ومختبر لـ GitLab CE webhook SSRF عبر نطاق 0.0.0.0/8 this-network، مما يتيح لـ Maintainer الوصول إلى loopback وقراءة الاستجابات من سجلات hook.
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 مع هذا النطاق على أنه "هذه الشبكة" وسيوجّه 0.0.0.1 إلى المكدس المحلي. يمكن لمسؤول مشروع (Maintainer) إنشاء webhook على http://0.0.0.1:<port>/ بينما يكون allow_local_requests_from_web_hooks_and_services معطلاً. يخزّن GitLab 8 KB من الاستجابة في سجلات الـ hook.
يمكن لمسؤول مشروعه الخاص أن يجعل GitLab يرسل POST إلى loopback عبر 0.0.0.1 ويقرأ محتوى الاستجابة من سجل الـ 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 (26212baacadb على شجرة v19.4.1-ee) |
| المصادقة | مسؤول مصادَق عليه (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>/. ثم التشغيل أو الاختبار. اقرأ response_body من واجهة hook events API. في إعداد omnibus الكل-في-واحد، يتشارك Sidekiq نفس netns الخاص بالحاوية مع Puma و Workhorse والمصدّرات (exporters). مسؤول مشروع خاصته لا ينبغي أن يقرأ تلك البيانات.
يبقى http://127.0.0.1/ و http://169.254.169.254/ محجوبين. هذه ليست ثغرة IMDS عبر link-local. إنها ثغرة "هذه الشبكة".
نفس المنتج، بقايا شقيقة: Gitea HTTPS import pin drop، Gitaly fetch omits resolved_address.
قرأت url_blocker.rb بعد إصلاحات SSRF في 19.4.1. الدالة validate_localhost تتحقق بدقة من "::" و "0.0.0.0" بالإضافة إلى Socket.ip_address_list. أما validate_loopback فهي ipv4_loopback? (127.0.0.0/8) و ipv6 loopback. تغطي الاختبارات الأسماء البديلة الثمانية/السداسية عشرية لـ 127.0.0.1. لكنها لا تذكر 0.0.0.1 أبداً.
الـ webhook هو مصرف المحتوى: WebHookService يرسل POST ويخزّن 8 KB. المسؤول يملك admin_web_hook.
قمت بتشغيل gitlab/gitlab-ce:19.4.1-ce.0 الأصلي. الطلبات المحلية معطّلة. أداة التقاط داخل netns الخاص بـ GitLab على 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 events : 201. يحتوي response_body في hook events على GITLAB-WEBHOOK-0NET-SSRF-WITNESS.
المنعطفات الخاطئة المسجّلة مسبقاً: /users/sign_in مع Accept: application/json يعطي 404؛ كلمة مرور seed التي تحتوي على root تُعد 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 تحتاج $$. reverse shell. مسرحية. الدليل هو 201 على 0.0.0.1، و 422 على 127.0.0.1، والشاهد في السجل.
cd lab
./run.sh
استهدف فقط http://127.0.0.1:18420. يحتاج GitLab CE إلى عدة GB من الذاكرة. مختبر مصرّح به فقط.
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
احجب 0.0.0.0/8 (و ::ffff:0.0.0.0/104) بنفس الطريقة التي يُحجب بها 127.0.0.0/8.
url_blocker.rb · web_hook_service.rb · project_hooks.rbGNU Affero GPL v3.0. راجع LICENSE. مختبر loopback فقط. دون أي ضمان.