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
Análisis: https://abraxaslabs.tech/research/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 volcado no tiene DownloadFunc. gitea_uploader.go abre entonces la URL file:// reescrita. os.Open sigue un enlace simbólico del lado del volcado. La unión permanece bajo el volcado. El enlace simbólico no.
La restauración por parte del operador de un volcado no confiable copia el destino del enlace simbólico en un adjunto de release. Como el uid de Gitea, eso es cualquier archivo que la cuenta de servicio pueda leer.
| ID | aún sin CVE |
| 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 |
| Autenticación | restauración por operador local de un volcado no confiable |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Entregar a un administrador un volcado no confiable (un directorio de "copia de seguridad", una restauración desde un remoto no confiable) que contenga un enlace simbólico bajo la ruta unida bajo el volcado. La restauración copia el archivo destino en un adjunto de release del repositorio restaurado. Quien pueda leer ese repositorio lo descarga.
Si el autor del volcado nombra app.ini, eso es robo de credenciales (INTERNAL_TOKEN, SECRET_KEY, JWT, contraseña de la BD). El oráculo del laboratorio fue un archivo testigo plantado, no app.ini. No es LPE del SO. No es no autenticado. No es escape de unión en la raíz del volcado. La migración en vivo desde GitHub/GitLab/Gitea establece DownloadFunc y no hace os.Open de un file:// del atacante.
Dos mapas de restauración estaban sobre la mesa de pendientes. Primero probé en el laboratorio el más obvio.
El hallazgo 8 era el escape de unión en la raíz del volcado: colocar un DownloadURL absoluto en release.yml, esperando que FilePathJoinAbs descartara el prefijo del volcado tal 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). La unión replicada de /tmp/dump + /tmp/GITEA-JOIN-OUTSIDE permaneció en /tmp/dump/tmp/GITEA-JOIN-OUTSIDE. La restauración reescribió file:// bajo el volcado y falló con open ...: no such file or directory. Eso no es escape de unión en esta etiqueta.
El hallazgo 9 es el que prosperó. Planté un archivo testigo fuera del volcado, coloqué un enlace simbólico en la ruta unida bajo el volcado, ejecuté gitea restore-repo. Restauración rc=0. El adjunto de release restaurado contenía GITEA-FILE-OPEN-WITNESS.
Callejones sin salida ya registrados: escape de unión en la raíz del volcado mediante un DownloadURL absoluto; LFI de file:// en migración en vivo; X-Gitea-Internal-Auth (la restauración es CLI como el uid de Gitea, no /api/internal); tratar la restauración HTTP como remota no autenticada (el CVSS es local, privilegio alto, interacción del usuario); una reverse shell. Teatro.
cd lab
./run.sh
Apunta solo a http://127.0.0.1:18137. Compose monta lab/dump en modo de solo lectura. run.sh coloca el testigo dentro del contenedor, construye el volcado, crea el enlace simbólico y luego ejecuta 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 enlaces simbólicos de volcado, y no restaurar volcados no confiables. El adjunto restaurado no debe contener el testigo fuera del volcado.
uri.go · gitea_uploader.go · FilePathJoinAbsGNU Affero GPL v3.0. Consulte LICENSE. Solo laboratorio de bucle invertido. Sin garantía.