Thumbnail oficial da máquina Anomaly no Hacking Club

Anomaly

Plataforma
HackingClub
Dificuldade
Easy
Sistema
Linux

Categorias Web · Exposed Credentials · Privesc

Máquina com SPA React que carrega a anon key do Supabase hardcoded no bundle JavaScript. Com a RLS desativada, a chave anônima lê todas as tabelas via PostgREST, incluindo uma credencial SSH em plaintext na tabela access_logs. A escalada de privilégio sai de cap_dac_override no binário do gdb, que permite escrever em /root/.ssh/authorized_keys.

Recon

Port scan

Começo com um scan de portas com o rustscan, deixando o -- -sV repassar o fingerprint de serviços ao nmap. Além do SSH e do Apache na 80, o alvo expõe o PostgreSQL na 5432, um pooler na 6543 e o gateway Kong nas 8000 e 8443. Kong na frente do PostgreSQL é a assinatura de um Supabase self-hosted.

rustscan -a anomaly.hc -- -sV
Saída do rustscan com SSH, Apache, PostgreSQL e o gateway Kong nas portas 8000/8443
Kong na frente do PostgreSQL — assinatura de Supabase self-hosted.

The application

A porta 80 serve a RHTech, uma plataforma de RH escrita como SPA React. A landing tem uma tela de login corporativo e nada mais acessível sem autenticação.

Página inicial da plataforma de RH RHTech com tela de login
RHTech — login corporativo e nada além sem autenticação.

Sem credenciais, o caminho é o frontend. O index.html é só um shell que carrega o bundle principal da aplicação em /assets/index-DAiJsT9Q.js, e é nesse bundle que mora a lógica de comunicação com o backend.

Foothold

JS bundle: hardcoded Supabase anon key

Toda SPA que fala com Supabase precisa instanciar o cliente em algum ponto do código, e esse cliente carrega a URL do projeto e a anon key. Como o bundle roda no navegador, as duas coisas chegam ao cliente. Baixo o bundle e procuro a chamada de inicialização: a URL http://anomaly.hc:8000 e a anon key (um JWT) estão fixas no código. Decodificando o JWT, o role é anon e o exp cai em 2123, ou seja, uma chave de longuíssima duração.

curl -s http://anomaly.hc/assets/index-DAiJsT9Q.js | grep -oE 'anomaly.hc:8000.{1,4}eyJ[A-Za-z0-9_.-]+'
cut -d. -f2 loot/.jwt | base64 -d | jq .
JWT da anon key decodificado mostrando role anon e exp em 2123
Anon key fixa no bundle — role anon, expira só em 2123.

A anon key não é um segredo por si só. Ela só vira problema quando a Row Level Security está desligada, porque é exatamente a RLS que deveria limitar o que o papel anon consegue ler.

RLS disabled: dump via PostgREST

O PostgREST expõe um documento OpenAPI na raiz de /rest/v1/, e a seção definitions lista todas as tabelas que o papel atual enxerga. Com a anon key, vejo quatro tabelas, entre elas settings e access_logs. Uma anon key conseguir enumerar e ler tabelas internas é o sinal claro de que a RLS não está aplicada.

curl -s "http://anomaly.hc:8000/rest/v1/" -H "apikey: $(cat loot/.jwt)" | jq ".definitions | keys"
Documento OpenAPI do PostgREST listando quatro tabelas, entre elas settings e access_logs
Quatro tabelas legíveis pela anon key — RLS desligada.

Levo a anon key para o Burp e consulto a tabela settings pelo Repeater. A resposta entrega a configuração da plataforma, incluindo o endereço do servidor de backup, o usuário desse backup (rhbackup) e o usuário administrativo do banco (supabase_admin).

Resposta da tabela settings com endereço do servidor de backup e os usuários rhbackup e supabase_admin
A tabela settings expõe os usuários rhbackup e supabase_admin.

access_logs: SSH credential in plaintext

A tabela access_logs registra ações da plataforma com um campo details em JSON. Filtrando pelas entradas SSH_BACKUP, uma delas guardou o comando completo de um backup automatizado por cron, com a senha do rhbackup embutida na própria linha do sshpass.

Entrada SSH_BACKUP da tabela access_logs com a senha do rhbackup embutida num comando sshpass
O log de backup gravou a senha do rhbackup em texto puro.

A credencial foi gravada apontando para o servidor de backup, mas o mesmo par usuário e senha autentica no host da própria aplicação. Reuso a senha no SSH de anomaly.hc e caio numa sessão como rhbackup.

Sessão SSH autenticada como rhbackup em anomaly.hc
A senha do backup também abre o SSH do host — sessão como rhbackup.

Com a sessão aberta, a flag de usuário está no home do rhbackup.

ls -la ~/user.txt
Listagem da flag user.txt no home do rhbackup
Flag de usuário no home do rhbackup.

Privilege Escalation

Enumeration: GDB with cap_dac_override

Capabilities são uma forma de dar a um binário um pedaço específico do poder do root sem o SUID inteiro, e é o tipo de coisa que vale procurar logo no início. Listando as capabilities do sistema, o gdb carrega cap_dac_override.

getcap -r / 2>/dev/null | grep -v snap
Saída do getcap mostrando cap_dac_override no binário do gdb
O gdb carrega cap_dac_override.

A cap_dac_override faz o processo ignorar as checagens de permissão de leitura, escrita e execução em qualquer arquivo, independente de dono ou modo. Num binário que embute um interpretador completo como o gdb (que tem Python integrado), isso vira uma primitiva de escrita arbitrária em todo o sistema de arquivos.

Exploit: writing to root’s authorized_keys

Em vez de tentar editar arquivos sensíveis na mão, deixo o Python do gdb fazer a escrita herdando a capability. Gero um par de chaves na minha máquina e uso o gdb para anexar a chave pública ao authorized_keys do root. O cap_dac_override cobre tanto a travessia de /root (modo 700) quanto a escrita no arquivo.

ssh-keygen -t ed25519 -f anomaly_root -N ''
gdb -batch -ex "python open('/root/.ssh/authorized_keys','a').write('<MINHA_CHAVE_PUBLICA>\n')"
gdb em modo batch executando Python para escrever no authorized_keys do root
O Python do gdb herda a capability e escreve no authorized_keys do root.

Com a chave plantada, autentico por SSH como root usando a chave privada correspondente.

Sessão SSH como root em anomaly.hc após plantar a chave
Login como root com a chave plantada.

A flag de root fecha a máquina.

ls -la /root/r0Ot.txt
Listagem da flag de root em /root/r0Ot.txt
Flag de root em /root.

Lessons Learned

  • A anon key do Supabase é pública por definição (vive no frontend). A barreira de segurança é a Row Level Security, e desligá-la transforma a chave anônima numa chave de leitura total do banco.
  • Dados operacionais nunca deveriam guardar credenciais. Logar a linha de comando inteira do backup gravou a senha do rhbackup em texto puro numa tabela legível pela anon key.
  • Reúso de credencial entre serviços (a senha do “backup server” valendo no host da aplicação) encurta a cadeia: um segredo destinado a uma máquina abriu outra.
  • cap_dac_override num binário com interpretador embutido (gdb, e seu Python) é equivalente a escrita arbitrária como root. Capabilities precisam ser tão auditadas quanto binários SUID.

Impact

  • A RHTech expõe dados pessoais de candidatos e funcionários: qualquer um com a anon key pública lê o banco inteiro de RH, um vazamento sujeito à LGPD.
  • Com root no host, o atacante controla aplicação, banco e os backups — perde-se a base confiável de recuperação, e conter o incidente exige reconstruir o servidor e rotacionar todas as credenciais de backup.

References

Acta
Medalha de São Bento — Crux Sancti Patris Benedicti