
GitLab CE 웹훅 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>/로 웹훅을 생성할 수 있습니다. GitLab은 응답의 8 KB를 훅 로그에 저장합니다.
자신의 프로젝트 Maintainer는 0.0.0.1을 통해 GitLab이 루프백으로 POST하도록 만들고 훅 로그에서 본문을 다시 읽어낼 수 있습니다.
| 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>/로 보냅니다. 트리거하거나 테스트합니다. 훅 이벤트 API에서 response_body를 읽습니다. 올인원 omnibus에서는 Sidekiq가 Puma, Workhorse, 익스포터와 컨테이너 netns를 공유합니다. 자신의 프로젝트 Maintainer가 그것들을 읽어서는 안 됩니다.
http://127.0.0.1/과 http://169.254.169.254/는 계속 차단됩니다. 이는 링크-로컬을 통한 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입니다. 스펙은 127.0.0.1의 8진수/16진수 별칭을 다룹니다. 0.0.0.1은 언급하지 않습니다.
웹훅이 본문 싱크입니다: WebHookService가 POST하고 8 KB를 저장합니다. Maintainer는 admin_web_hook을 가집니다.
기본 gitlab/gitlab-ce:19.4.1-ce.0을 띄웠습니다. 로컬 요청 꺼짐. GitLab netns 내부의 0.0.0.1:18080에 캐처. 훅 생성 http://127.0.0.1:18080/과 http://169.254.169.254/ : 422 Invalid url given. 훅 생성 http://0.0.0.1:18080/ : 201. 푸시 이벤트 테스트 : 201. 훅 이벤트 response_body에 GITLAB-WEBHOOK-0NET-SSRF-WITNESS가 포함됨.
이미 기록된 우회로들: Accept: application/json을 사용한 /users/sign_in은 404; root를 포함한 시드 비밀번호는 WeakPasswords; lo에 0.0.0.1을 추가하면 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를 참조하십시오. 루프백 랩 전용. 보증 없음.