Proof-of-Concept für eine Gitea-Restore-Repo-file://-Local-File-Inclusion (CWE-73), die Symlink-Ziele außerhalb eines nicht vertrauenswürdigen Dumps in Release-Anhänge kopiert.
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 hat einen hostmatcher-HTTP-Client an OpenWithClient angebunden. Der file://-Zweig ignoriert diesen Client und ruft os.Open auf. Die Wiederherstellung eines Dumps hat kein DownloadFunc. gitea_uploader.go öffnet dann die umgeschriebene file://-URL. os.Open folgt einem Symlink auf der Dump-Seite. Der Join bleibt unterhalb des Dumps. Der Symlink nicht.
Die Wiederherstellung eines nicht vertrauenswürdigen Dumps durch einen Operator kopiert das Symlink-Ziel in einen Release-Anhang. Unter der Gitea-UID ist das jede Datei, die das Dienstkonto lesen kann.
| ID | noch keine CVE |
| CWE | CWE-73 |
| CVSS | Mittel: 4.2 CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N |
| Produkt | Gitea |
| Betroffen | bis v1.27.3 (146cc3e); lokaler Operator gitea restore-repo |
| Auth | lokale Operator-Wiederherstellung eines nicht vertrauenswürdigen Dumps |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Einem Admin einen nicht vertrauenswürdigen Dump unterjubeln (ein „Backup“-Verzeichnis, eine Wiederherstellung von einem nicht vertrauenswürdigen Remote), der einen Symlink unter dem unterhalb-des-Dumps-gejointen Pfad enthält. Die Wiederherstellung kopiert die Zieldatei in einen Release-Anhang des wiederhergestellten Repos. Wer dieses Repo lesen kann, lädt sie herunter.
Wenn der Dump-Autor app.ini benennt, ist das Credential-Diebstahl (INTERNAL_TOKEN, SECRET_KEY, JWT, DB-Passwort). Das Lab-Orakel war eine platzierte Witness-Datei, nicht app.ini. Es ist keine OS-LPE. Es ist nicht unauthentifiziert. Es ist kein Join-Escape aus dem Dump-Root. Live-Migration von GitHub/GitLab/Gitea setzt DownloadFunc und ruft nicht os.Open auf einer vom Angreifer stammenden file:// auf.
Zwei Restore-Maps lagen auf dem übrig gebliebenen Tisch. Ich habe zuerst die offensichtliche im Lab getestet.
Finding 8 war ein Join-Escape aus dem Dump-Root: eine absolute DownloadURL in release.yml platzieren und hoffen, dass FilePathJoinAbs das Dump-Präfix so verwirft, wie es ein naives filepath.Join(base, "/etc/passwd") tut. Auf 1.27.3 tut es das nicht. Jedes sub ist filepath.Clean("/"+s) und dann filepath.Join(base, cleaned). Der Replica-Join von /tmp/dump + /tmp/GITEA-JOIN-OUTSIDE blieb /tmp/dump/tmp/GITEA-JOIN-OUTSIDE. Die Wiederherstellung schrieb file:// unter den Dump um und scheiterte mit open ...: no such file or directory. Das ist auf diesem Tag kein Join-Escape.
Finding 9 ist das, was gelandet ist. Ich habe eine Witness-Datei außerhalb des Dumps platziert, einen Symlink am unterhalb-des-Dumps-gejointen Pfad angelegt und gitea restore-repo ausgeführt. Restore rc=0. Der wiederhergestellte Release-Anhang enthielt GITEA-FILE-OPEN-WITNESS.
Bereits dokumentierte Irrwege: Join-Escape aus dem Dump-Root über eine absolute DownloadURL; Live-Migrate-file://-LFI; X-Gitea-Internal-Auth (Restore ist CLI als die Gitea-UID, nicht /api/internal); Restore-HTTP als remote-unauthentifiziert behandeln (CVSS ist lokal, hohe Privilegien, Benutzerinteraktion); eine Reverse Shell. Theater.
cd lab
./run.sh
Ziele nur auf http://127.0.0.1:18137. Compose bindet lab/dump schreibgeschützt ein. run.sh platziert den Zeugen im Container, erstellt den Dump, legt den Symlink an und führt dann gitea restore-repo aus.
restore-rc=0 Restore repo labadmin/restored successfully
download status=200 snippet=GITEA-FILE-OPEN-WITNESS
SUCCESS GITEA-RESTORE-FILE-OPEN
file:// aus OpenWithClient entfernen oder die Verfolgung von Dump-Symlinks verweigern und keine nicht vertrauenswürdigen Dumps wiederherstellen. Ein wiederhergestellter Anhang darf keinen Zeugen außerhalb des Dumps enthalten.
uri.go · gitea_uploader.go · FilePathJoinAbsGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.