Proof-of-concept per una vulnerabilità di tipo local file inclusion (CWE-73) in Gitea restore-repo tramite file://, che copia i target dei symlink al di fuori di un dump non attendibile all'interno degli allegati di release.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitea-restore-file-open
Write-up: https://abraxaslabs.tech/research/gitea-restore-file-open
Gitea 1.27.3 - Gitea
CVE-2026-59765 ha aggiunto un client HTTP hostmatcher su OpenWithClient. Il ramo file:// ignora quel client e chiama os.Open. Il ripristino di un dump non ha DownloadFunc. gitea_uploader.go apre quindi l'URL file:// riscritto. os.Open segue un symlink lato dump. Il join resta sotto il dump. Il symlink no.
Il ripristino da parte dell'operatore di un dump non attendibile copia il target del symlink in un allegato di release. Con l'uid di Gitea, si tratta di qualsiasi file che l'account di servizio può leggere.
| ID | nessuna CVE al momento |
| CWE | CWE-73 |
| CVSS | Medium: 4.2 CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N |
| Prodotto | Gitea |
| Affetto | fino alla v1.27.3 (146cc3e); operatore locale gitea restore-repo |
| Auth | ripristino da parte dell'operatore locale di un dump non attendibile |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Consegnare a un amministratore un dump non attendibile (una directory di "backup", un ripristino da un remoto non attendibile) che contiene un symlink sotto il percorso unito sotto il dump. Il ripristino copia il file target in un allegato di release sul repository ripristinato. Chiunque possa leggere quel repository può scaricarlo.
Se l'autore del dump indica app.ini, si tratta di furto di credenziali (INTERNAL_TOKEN, SECRET_KEY, JWT, password del DB). L'oracolo del lab era un file testimone piantato, non app.ini. Non è un LPE a livello di OS. Non è non autenticato. Non è un'evasione del join dalla radice del dump. La migrazione live da GitHub/GitLab/Gitea imposta DownloadFunc e non esegue os.Open su un file:// controllato dall'attaccante.
Due mappe di ripristino giacevano sulla tabella dei residui. Ho testato in lab prima quella ovvia.
Il finding 8 era l'evasione del join dalla radice del dump: inserire un DownloadURL assoluto in release.yml, sperando che FilePathJoinAbs elimini il prefisso del dump come farebbe un ingenuo filepath.Join(base, "/etc/passwd"). Su 1.27.3 non lo fa. Ogni sub è filepath.Clean("/"+s) e poi filepath.Join(base, cleaned). Il join replicato di /tmp/dump + /tmp/GITEA-JOIN-OUTSIDE è rimasto /tmp/dump/tmp/GITEA-JOIN-OUTSIDE. Il ripristino ha riscritto file:// sotto il dump e ha fallito con open ...: no such file or directory. Non è un'evasione del join su questo tag.
Il finding 9 è quello che ha funzionato. Ho piantato un file testimone fuori dal dump, ho inserito un symlink nel percorso unito sotto il dump, ho eseguito gitea restore-repo. Ripristino rc=0. L'allegato di release ripristinato conteneva GITEA-FILE-OPEN-WITNESS.
Vicoli ciechi già registrati: evasione del join dalla radice del dump tramite un DownloadURL assoluto; LFI file:// in migrazione live; X-Gitea-Internal-Auth (il ripristino è CLI con l'uid di Gitea, non /api/internal); trattare l'HTTP di ripristino come remoto non autenticato (il CVSS è locale, privilegi elevati, interazione utente); una reverse shell. Teatro.
cd lab
./run.sh
Prendi di mira solo http://127.0.0.1:18137. Compose monta lab/dump in sola lettura. run.sh inserisce il witness all'interno del container, costruisce il dump, crea il symlink, poi esegue gitea restore-repo.
restore-rc=0 Restore repo labadmin/restored successfully
download status=200 snippet=GITEA-FILE-OPEN-WITNESS
SUCCESS GITEA-RESTORE-FILE-OPEN
Rimuovere file:// da OpenWithClient, oppure rifiutarsi di seguire i symlink dei dump, e non ripristinare dump non attendibili. L'allegato ripristinato non deve contenere il witness fuori dal dump.
uri.go · gitea_uploader.go · FilePathJoinAbsGNU Affero GPL v3.0. Vedi LICENSE. Solo laboratorio su loopback. Nessuna garanzia.