CVE-2026-58442
Gitea: Repository migration SSRF via multi-answer DNS allow-list bypass
Summary
### Summary Gitea's repository migration URL validation can be bypassed when a migration hostname resolves to multiple IP addresses. The validation logic accepts the destination if **any** resolved IP is allowed, even if another resolved IP is loopback, private, or otherwise blocked. The later `git clone` operation resolves the hostname again outside of that validation decision, so it can connect to the internal address. An authenticated low-privilege user who can create repository migrations can use an attacker-controlled DNS name to make Gitea connect to internal-only Git services and import their contents into a repository controlled by the attacker. ### Details The issue is in `services/migrations/migrate.go`, in the migration allow/block-list check. Current logic computes whether any resolved IP is allowed: ```go var ipAllowed bool var ipBlocked bool for _, addr := range addrList { ipAllowed = ipAllowed || allowList.MatchIPAddr(addr) ipBlocked = ipBlocked || blockList.MatchIPAddr(addr) } ``` Then, when an allow-list is active, the host is accepted if the hostname matches or `ipAllowed` is true: ```go if !allowList.IsEmpty() { if !allowList.MatchHostName(hostName) && !ipAllowed { return &git.ErrInvalidCloneAddr{Host: hostName, IsPermissionDenied: true} } } ``` This means a hostname resolving to both: - an allowed public IP, e.g. `1.2.3.4` - a blocked internal IP, e.g. `127.0.0.1` passes validation because the public IP sets `ipAllowed = true`. The actual repository import is later performed by `git clone --mirror` via `MigrateRepositoryGitData` / `gitrepo.CloneExternalRepo`. That git subprocess performs its own DNS resolution and is not tied to the specific IP set that was validated earlier. If the hostname resolves, rotates, or is re-bound to the internal address at clone time, Gitea can connect to a destination the migration filter would reject if supplied directly. The direct internal URL is correctly blocked, but the multi-answer hostname is accepted. ### PoC I verified the vulnerable predicate locally against Gitea checkout: ```text e8654c7e062431a521636703f47339cde64644fd ``` using Dockerized Go tests with `golang:1.26.4`. The local test proves: - `checkByAllowBlockList("loopback.example.test", [127.0.0.1])` is rejected. - `checkByAllowBlockList("mixed.example.test", [1.2.3.4, 127.0.0.1])` is accepted. Minimal reproducer at the validation layer: ```go func TestMigrationMultiAnswerAnyAllowed(t *testing.T) { old := setting.Migrations t.Cleanup(func() { setting.Migrations = old }) setting.Migrations.AllowedDomains = "" setting.Migrations.BlockedDomains = "" setting.Migrations.AllowLocalNetworks = false require.NoError(t, Init()) err := checkByAllowBlockList("mixed.example.test", []net.IP{ net.ParseIP("1.2.3.4"), net.ParseIP("127.0.0.1"), }) require.NoError(t, err, "mixed public+loopback answers should currently pass") err = checkByAllowBlockList("loopback.example.test", []net.IP{ net.ParseIP("127.0.0.1"), }) require.Error(t, err, "loopback-only answer should be rejected") } ``` To reproduce end-to-end: 1. Run Gitea with repository migration enabled and `ALLOW_LOCALNETWORKS = false`. 2. Create a normal non-admin user that can create repositories. 3. Run an internal Git HTTP service reachable only from the Gitea server, for example on `127.0.0.1:18082`. 4. Configure an attacker-controlled hostname so that DNS can return both a public address and `127.0.0.1`, or can return a public address during Gitea's pre-flight validation and `127.0.0.1` during the later git clone. 5. Confirm the direct internal migration is rejected: ```bash curl -X POST http://GITEA/api/v1/repos/migrate \ -H "Authorization: token USER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "clone_addr": "http://127.0.0.1:18082/repo.git", "repo_name": "direct-internal", "service": "git", "private": true }' ```
Affected packages
| Ecosystem | Package | Affected versions | Fixed versions |
|---|---|---|---|
| Go | code.gitea.io/gitea | — | 1.27.0 |
Remediation: Upgrade to 1.27.0 or later.
References
Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.
CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.