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
Gitea 1.27.3 - Gitea
CVE-2026-59765 ha inserito 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 rimane 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. Come uid di Gitea, ovvero 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 admin 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 di destinazione in un allegato di release sul repository ripristinato. Chiunque possa leggere quel repository lo scarica.
Se l'autore del dump lo chiama 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 è OS LPE. 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:// dell'attaccante.
Due mappe di ripristino giacevano sulla tabella rimanente. Ho testato in lab quella ovvia per prima.
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 fa il naive filepath.Join(base, "/etc/passwd"). Su 1.27.3 non lo fa. Ogni sub è filepath.Clean("/"+s) 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 ed è 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 come uid di Gitea, non /api/internal); trattare il ripristino HTTP come remoto non autenticato (il CVSS è locale, privilegio elevato, interazione utente); una reverse shell. Teatro.
cd lab
./run.sh
Target solo http://127.0.0.1:18137. Compose monta lab/dump in sola lettura. run.sh pianta il testimone all'interno del container, costruisce il dump, inserisce il symlink, poi 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 del dump, e non ripristinare dump non attendibili. L'allegato ripristinato non deve contenere il testimone fuori dal dump.
uri.go · gitea_uploader.go · FilePathJoinAbsGNU Affero GPL v3.0. Vedi LICENSE. Solo lab su loopback. Nessuna garanzia.