Prueba de concepto para una inclusión de archivos locales file:// en la restauración de repositorios de Gitea (CWE-73) que copia los destinos de enlaces simbólicos fuera de un volcado no confiable hacia los archivos adjuntos de la versión.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitea-restore-file-open
Gitea 1.27.3 - Gitea
CVE-2026-59765 colocó un cliente HTTP hostmatcher en OpenWithClient. La rama file:// ignora ese cliente y llama a os.Open. La restauración de un dump no tiene DownloadFunc. gitea_uploader.go entonces abre la URL file:// reescrita. os.Open sigue un symlink del lado del dump. El join permanece bajo el dump. El symlink no.
La restauración por parte del operador de un dump no confiable copia el destino del symlink en un adjunto de release. Como el uid de Gitea, eso es cualquier archivo que la cuenta de servicio pueda leer.
| ID | sin CVE aún |
| CWE | CWE-73 |
| CVSS | Medio: 4.2 CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N |
| Producto | Gitea |
| Afectado | hasta v1.27.3 (146cc3e); operador local gitea restore-repo |
| Auth | restauración por operador local de un dump no confiable |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Entregar a un admin un dump no confiable (un directorio de "backup", una restauración desde un remoto no confiable) que contenga un symlink bajo la ruta unida bajo el dump. La restauración copia el archivo destino en un adjunto de release en el repositorio restaurado. Quien pueda leer ese repositorio lo descarga.
Si el autor del dump nombra app.ini, eso es robo de credenciales (INTERNAL_TOKEN, SECRET_KEY, JWT, contraseña de la BD). El oráculo del lab fue un archivo testigo plantado, no app.ini. No es LPE del SO. No es no autenticado. No es escape del join de la raíz del dump. La migración en vivo desde GitHub/GitLab/Gitea establece DownloadFunc y no hace os.Open sobre un file:// del atacante.
Dos mapas de restauración quedaron sobre la tabla sobrante. Probé en el lab el obvio primero.
El hallazgo 8 era escape del join de la raíz del dump: poner un DownloadURL absoluto en release.yml, esperar que FilePathJoinAbs descartara el prefijo del dump como lo hace el ingenuo filepath.Join(base, "/etc/passwd"). En 1.27.3 no lo hace. Cada sub es filepath.Clean("/"+s) y luego filepath.Join(base, cleaned). El join de réplica de /tmp/dump + /tmp/GITEA-JOIN-OUTSIDE permaneció en /tmp/dump/tmp/GITEA-JOIN-OUTSIDE. La restauración reescribió file:// bajo el dump y falló con open ...: no such file or directory. Eso no es escape del join en este tag.
El hallazgo 9 es el que dio resultado. Planté un archivo testigo fuera del dump, puse un symlink en la ruta unida bajo el dump, ejecuté gitea restore-repo. Restauración rc=0. El adjunto de release restaurado contenía GITEA-FILE-OPEN-WITNESS.
Caminos equivocados ya registrados: escape del join de la raíz del dump mediante un DownloadURL absoluto; LFI file:// en migración en vivo; X-Gitea-Internal-Auth (la restauración es CLI como el uid de Gitea, no /api/internal); tratar el HTTP de restauración como no autenticado remoto (el CVSS es local, alto privilegio, interacción del usuario); una reverse shell. Teatro.
cd lab
./run.sh
Objetivo solo http://127.0.0.1:18137. Compose monta lab/dump en solo lectura. run.sh planta el testigo dentro del contenedor, construye el dump, coloca el symlink, luego gitea restore-repo.
restore-rc=0 Restore repo labadmin/restored successfully
download status=200 snippet=GITEA-FILE-OPEN-WITNESS
SUCCESS GITEA-RESTORE-FILE-OPEN
Eliminar file:// de OpenWithClient, o negarse a seguir symlinks del dump, y no restaurar dumps no confiables. El adjunto restaurado no debe contener el testigo fuera del dump.
uri.go · gitea_uploader.go · FilePathJoinAbsGNU Affero GPL v3.0. Ver LICENSE. Solo lab en loopback. Sin garantía.