
PoC e write-up para a escalada de privilégios do "Nexus" do HackTheBox: serviço de sincronização de templates do Gitea executado como root, vulnerável a path traversal através de objetos Git forjados. Apenas para uso educacional.
Path traversal no template-sync do Gitea via objetos Git forjados → root
Um exploit de prova de conceito para escalação de privilégios na máquina HackTheBox Nexus.
Um serviço systemd pertencente ao root sincroniza repositórios de template do Gitea para o disco
sem sanitizar os nomes das entradas, permitindo que um usuário autenticado de baixo privilégio
escreva arquivos arbitrários como root através de directory traversal — usado aqui para implantar
uma chave SSH em /root/.ssh/authorized_keys.
Este código é fornecido para fins educacionais e testes de segurança autorizados apenas — ou seja, ambientes de laboratório como o HackTheBox, ou sistemas que você possui ou tem permissão escrita explícita para testar. O uso não autorizado contra sistemas que você não controla é ilegal. O autor não assume nenhuma responsabilidade pelo uso indevido.
Se a máquina Nexus ainda estiver ativa no HackTheBox, não publique nem compartilhe este exploit — as regras do HTB proíbem a distribuição de soluções para máquinas ativas.
| Classe | Path Traversal (CWE-22) → Escalação de Privilégios Local |
| Componente | gitea-template-sync.service (custom /etc/gitea/template-sync.py) |
| Executa como | root, acionado a cada ~60s por gitea-template-sync.timer |
| Causa raiz | os.path.join() em nomes não sanitizados obtidos de objetos Git tree |
| Impacto | Usuário autenticado de baixo privilégio → root |
O script de sincronização lê arquivos de repositórios marcados como templates no Gitea e os escreve no sistema de arquivos local. O caminho de destino é construído aproximadamente assim:
dest = os.path.join(base_dir, name) # name comes straight from the Git tree
open(dest, "w").write(content)
os.path.join() não neutraliza sequências ... Uma entrada de tree cujo nome
é um caminho de traversal (por exemplo, ../../../../../root/.ssh/authorized_keys) portanto
faz com que o processo privilegiado escreva fora do diretório pretendido.
O Git armazena conteúdo e nomes separadamente:
name → object (é aqui que o nome do arquivo reside);O nome do arquivo em uma entrada de tree é apenas texto, então ele pode ser .. — mas o cliente
Git padrão recusa criar tal entrada, justamente para prevenir este ataque.
Este PoC contorna essa verificação do lado do cliente escrevendo objetos Git brutos diretamente
em .git/objects/ (calculando os ids SHA-1 manualmente) e criando refs/heads/main
manualmente. O repositório enviado então contém uma entrada de tree chamada .., que o
processo de sincronização root resolve e segue.
www-data..env de produção, reutilizadas como
senha de sistema → SSH como um usuário de baixo privilégio.root.O tema recorrente nas três etapas: um processo privilegiado confiando em dados controlados pelo atacante sem validação (um tipo MIME falsificado, uma senha reutilizada, um nome de arquivo não sanitizado).
http://localhost:3000.git, python3, curl e ssh disponíveis (padrão na máquina).chmod +x nexus_privesc.sh
./nexus_privesc.sh -t <TARGET_IP> -u <GITEA_USER> -p <GITEA_PASS> [-d DEPTH]
Exemplo:
./nexus_privesc.sh -t 10.129.21.192 -u jones -p 'REDACTED' -d 5
/tmp/.exploit_key)... que mapeia para
/root/.ssh/authorized_keys, com a chave pública do atacante como conteúdo.root com a chave implantada.Monitore a sincronização no alvo com:
tail -f /var/log/template-sync.log
Uma execução bem-sucedida registra uma linha como:
synced: ../../../../../root/.ssh/authorized_keys
DEPTHDEPTH é o número de níveis .. necessários para subir do diretório de
sincronização/staging até a raiz do sistema de arquivos (/). Se a sincronização for bem-sucedida no log mas o
SSH root falhar, o traversal caiu no nível errado — tente novamente com -d 4 ou
-d 6. O valor exato pode ser confirmado lendo o caminho de staging em
/etc/gitea/template-sync.py.
Nunca construa caminhos de sistema de arquivos a partir de nomes não confiáveis apenas com os.path.join().
Normalize e confine os caminhos, por exemplo:
base = os.path.realpath(base_dir)
dest = os.path.realpath(os.path.join(base, name))
if not dest.startswith(base + os.sep):
raise ValueError("path traversal blocked")
Rejeite nomes de entradas de tree que contenham .., caminhos absolutos ou separadores de caminho.
Execute o serviço de sincronização sob uma conta dedicada de baixo privilégio, não root.
Habilite receive.fsckObjects = true no Gitea para rejeitar objetos enviados malformados.
<your name / handle> — para uso educacional no HackTheBox.
| Flag | Descrição | Padrão |
|---|
-t | IP alvo (para o SSH root final) | — (obrigatório) |
-u | Nome de usuário do Gitea | — (obrigatório) |
-p | Senha do Gitea | — (obrigatório) |
-d | Número de níveis .. para alcançar / a partir do diretório de sincronização | 5 |