Gitea
CVE-2026-58441
MEDIUM
Severity by source
AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N
AV:L because exploitation requires local CLI execution on the Gitea host; S:C because SSRF extends impact to internal network services and cloud metadata endpoints beyond the Gitea process.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
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:
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:
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:
err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
// ... later: fetch from that remoteresulting 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
- Create a dump directory following the normal
restore-repolayout
(repo.yml, etc.), and add a pull_request.yml containing at least one entry with:
- number: 1
head:
cloneURL: "http://<attacker-controlled-or-internal-host>:<port>/ssrf-proof"
ref: "main"- Run
gitea restore-repoagainst that dump directory for any repo
owner.
- Observe on the target host/listener: an actual
gitHTTP
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.
AnalysisAI
Server-Side Request Forgery in Gitea's restore-repo CLI command allows an attacker who can supply a malicious dump archive to a Gitea administrator to force the server to issue git fetch against arbitrary attacker-controlled or internal URLs, including cloud metadata endpoints and local filesystem paths via file://. The root cause is a Go language logic defect: CheckAndEnsureSafePR is invoked with an empty commonCloneBaseURL string, causing strings.HasPrefix(anything, "") to always return true, which silently disables the URL validation guard for Head.CloneURL in the restore path only. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | The `gitea restore-repo` CLI command must be executed by a Gitea administrator on the server host, processing a dump directory that the attacker controls or has tampered with. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The provided CVSS 3.1 score of 6.3 (Medium) with vector AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N is internally consistent: the attack vector is local (CLI invocation on the Gitea server host), complexity is low once triggered, no attacker privileges are required, but an administrator must actively run the restore command (UI:R). … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An attacker crafts a minimal Gitea dump directory containing a `pull_request.yml` with `head.cloneURL` set to `http://169.254.169.254/latest/meta-data/iam/security-credentials/` (the AWS IMDSv1 endpoint). The dump is delivered to a Gitea administrator under the guise of a legitimate repository migration from a partner organization. … |
| Remediation | Upgrade Gitea to v1.27.0 or later, which resolves the missing URL validation in the `restore-repo` path. … Detailed patch versions, workarounds, and compensating controls in full report. |
Threat intelligence, references, and detailed analysis are available after sign-in.
Broken access control in Gitea's Composer package registry (versions up to and including 1.26.1) lets remote attackers r
Gitea before 1.16.7 does not escape git fetch remote. Rated high severity (CVSS 7.5), this vulnerability is remotely exp
The git hook feature in Gitea 1.1.0 through 1.12.5 might allow for authenticated remote code execution in customer envir
Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting') vulnerability in Gitea Gitea
Container escape in Gitea act_runner (Docker backend, through act 0.262.0) lets an authenticated user with workflow-exec
models/repo_mirror.go in Gitea before 1.7.6 and 1.8.x before 1.8-RC3 mishandles mirror repo URL settings, leading to rem
An issue was discovered in Gitea through 1.11.5. Rated high severity (CVSS 7.5), this vulnerability is remotely exploita
Missing Authorization in GitHub repository go-gitea/gitea prior to 1.16.4. Rated high severity (CVSS 7.1), this vulnerab
Open Redirect on login in GitHub repository go-gitea/gitea prior to 1.16.5. Rated medium severity (CVSS 6.1), this vulne
Reverse-proxy authentication bypass in the official Gitea Docker image (versions up to and including 1.26.2) allows any
Branch-protection bypass in Gitea's self-hosted Git server (all versions before 1.26.0) allows a user with push access t
Migration transport protections in Gitea are bypassed for Git LFS operations, affecting all self-hosted instances before
Same weakness CWE-918 – Server-Side Request Forgery (SSRF)
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-xmj7-xj85-hfc3