# Gitea: Repository Migration Follows Git HTTP Redirects After URL Allow/Block Validation, Enabling Internal Git Repository Exfiltration

Gitea validates migration URLs but lets Git follow HTTP redirects, so a low-privileged user can clone internal Git repos into their own Gitea repository.

- Payout: Research
- Severity: high
- Program: Gitea
- Found by (vendor credit): cyberlanc3r
- Found by: cyberlanc3r, tomchuoi, danieltk76, DshtAnger
- Disclosed: 2026-07-21
- CVE: CVE-2026-57894
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-82f7-87hm-852x
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-82f7-87hm-852x
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:access-control vuln:ssrf

**Target:** Repository migration / pull mirror (git clone, git fetch)
**Root cause:** Gitea applies its migration allow/block policy to the originally submitted URL, then delegates the clone/fetch to the Git CLI which defaults to http.followRedirects=initial and follows the first redirect without revalidation.
**Steps:**
1. Host a public endpoint `https://attacker.example/public.git` that answers the initial Git discovery request with an HTTP 302 to `http://internal-git.company.local/team/private.git/info/refs?service=git-upload-pack`.
2. Submit that public URL via `POST /repo/migrate` or `POST /api/v1/repos/migrate`; `IsMigrateURLAllowed` resolves and approves only `attacker.example`.
3. Gitea runs `git clone --mirror` on the URL; Git follows the redirect and uses the internal URL as base for `/info/refs`, `/HEAD` and object requests.
4. Internal repo content lands in the attacker-owned Gitea repository; browse/archive/clone it and grep history for secrets.
5. For persistence, enable mirror mode so scheduled `git fetch --tags` keeps following the redirect and pulls future internal commits.
6. Verify mitigation: `git -c http.followRedirects=false clone` fails with `The requested URL returned error: 302`.
**How found:** Noticed a validation/enforcement mismatch: policy is applied to the submitted URL in Go code, but the actual network sink is a Git subprocess. Checked Git's documented default `http.followRedirects=initial` and confirmed with a local redirector + loopback Git target that clone and `git fetch --tags` both follow the redirect, while `http.followRedirects=false` blocks it.
**Exploitation:** Attacker needs a low-privileged account (or open registration) and a redirector they control. The redirector 302s Git's first request to an internal Git HTTP endpoint reachable from the Gitea server; Git then fetches all objects from the internal target and Gitea imports them into the attacker's repo. Mirror mode repeats the fetch on schedule, continuously exfiltrating new commits. Recovered secrets (CI vars, cloud keys, kubeconfigs, Terraform state, deploy keys, PATs) enable downstream CI/CD or cloud compromise.
**Escalation:** Pull mirror converts one-shot import into ongoing exfiltration of future commits; harvested credentials chain to CI/CD, cloud, Kubernetes or runner code execution. Actions runner execution only if operator enabled Actions on mirrors (not default).
**Insight:** When a server validates a URL then hands it to an external CLI (git, curl, wget, package managers), the tool's own redirect/proxy defaults become the real policy boundary — test first-hop 302s to loopback/RFC1918 and check whether scheduled background jobs (mirrors, syncs) reuse the same unvalidated sink.

## Proof of concept (copied exactly from the original report)

**git clone follows redirect to internal repo**

```
code: 0
redirector hit count: 1
target hit count: 5
cloned content: "internal repo data via redirected git clone"
```

**git fetch --tags follows redirect in mirror**

```
code: 0
target object requests observed: 5
hasFetchedBranchRef: true
```

**http.followRedirects=false blocks redirect clone**

```
fatal: unable to access '<redirector-url>': The requested URL returned error: 302
```

