Explorando desserialização insegura via SSRF Protocol Smuggling
Nesse artigo, veremos um cenário onde podemos obter RCE explorando desserialização insegura via SSRF Protocol Smuggling. É um cenário bem complexo de ser explorado, e requer um conhecimento sobre desserialização insegura e SSRF Protocol Smuggling. Recomendo que você leia os artigos anteriores sobre desserialização insegura, SSRF e Protocol Smuggling.
Conhecendo o cenário e a infraestrutura
Um cenário ideal para essa exploração, seria uma aplicação que armazena suas sessões serializadas no Redis, como exemplo o Laravel, pois ele utiliza sessões serializadas, e é comum utilizar o Redis para armazenar suas sessões.
Em um cenário desses, se conseguirmos um SSRF e utilizarmos a técnica de Protocol Smuggling, podemos acessar o Redis para alterar o conteúdo da nossa sessão, assim explorando uma desserialização insegura.
Na imagem acima, vemos que o Nginx faz forwarding da porta 127.0.0.1:8000 para *:80, assim expondo o Laravel para a internet. Vemos que o redis está disponível apenas para a rede interna, então para acessarmos ele, precisamos de um SSRF e utilizarmos a técnica de Protocol Smuggling.
Acessando Redis via Protocol Smuggling
Para executarmos comandos no Redis com o protocolo gopher, precisamos inserir o CRLF no final de cada comando e fazermos double-urlencode (o double-urlencode é necessário em algumas ocasiões, como webapps, que geralmente fazem 1 decode por padrão, então precisamos fazer 2 encodes na payload). Criei um script em python simples que gera a payload automaticamente:
Executando o script acima: python3 script.py redis "keys *"
, obtemos o output:
gopher://redis:6379/_keys%2520%252A%250D%250Aquit%250D%250A
Enviando a payload em uma vulnerabilidade de SSRF, vemos que o Redis nos retorna o output do comando executado:
Explorando desserialização insegura via Protocol Smuggling:
Para esse artigo, criamos um laboratório para você colocar em prática esse conteúdo. O link do repositório no github está no final do artigo.
Entrando na página inicial, vemos que a aplicação está utilizando o Laravel:
Fazendo fuzzing, vemos que o .env está exposto na aplicação:
A aplicação está utilizando o redis para armazenar suas sessões. Vemos que o endereço IP do redis é "redis", pois essa aplicação deve estar configurada em uma rede do docker-composer.
Na página principal, vemos um campo para inserir uma URL para verificar se ela está funcional ou não. Enviando uma URL e interceptando a requisição:
Vemos que a aplicação possibilita uma requisição para localhost, então pode ser que esteja vulnerável à SSRF. Enviando uma requisição utilizando o protocolo gopher:
Veja que a aplicação aceitou o protocolo gopher e conseguiu estabelecer uma comunicação raw socket com o netcat. Podemos utilizar o script que criamos anteriormente para tentar comunicação com o Redis:
Com acesso ao redis, podemos inserir um gadget na nossa sessão para obtermos RCE no laravel através da desserialização insegura. Para gerarmos esses "gadgets" podemos utilizar o phpggc. Fazendo um clone do repositório, podemos executar a ferramenta:
Essa é a lista de gadgets disponíveis para explorar desserialização insegura no Laravel. Gostamos muito do gadget Laravel/RCE10, pois ele não possui null bytes na payload, assim facilitando a exploração. Gerando a payload:
Ao inserir essa payload na nossa sessão, o comando "id" vai ser executado no sistema. Para inserirmos essa payload na sessão precisamos executar o comando "set <KEY> <PAYLOAD>" no redis através do SSRF. Primeiro precisamos listar as keys do Laravel para identificar a key que está armazenando a nossa sessão, então podemos executar um "flushall", assim limpando todas as keys do redis, e em seguida recarregar a página para gerar outra sessão:
Recarregando a página no navegador geramos outra sessão, então terá apenas 1 key no redis:
Porém, precisamos gerar outra sessão, pois quando estava realizando alguns testes, descobrimos que ao enviar uma requisição para o Laravel, ele reseta o conteúdo da sua sessão caso ele esteja alterado ou inválido, e isso deu conflito com o ataque, pois para alterar o conteúdo precisamos enviar uma request, assim ele altera e logo depois reseta... Sim é algo confuso. Para gerarmos outra sessão, podemos entrar na webapp pela guia anônima:
Listando as keys do redis novamente:
Vemos que uma segunda key foi criada. Inserindo a payload na sessão:
Esse erro aconteceu porque não estamos utilizando o formato RESP para enviarmos os comandos para o Redis. O formato RESP utiliza a seguinte sintaxe:
Para executar keys *
*2
$4
keys
$1
*
Primeiro precisamos especificar quantas palavras compõe o comando, depois precisamos especificar a quantidade de letras de cada palavra e em seguida a palavra. Então alteramos o script para fazer isso:
import urllib.parse
import sys
def generate_payload_redis(command):
command = command.split(" ")
payload = f"*{len(command)}\r\n"
for word in command:
payload += f"${len(word)}\r\n"
payload += f"{word}\r\n"
return payload
def generate_payload(ip, command):
command = generate_payload_redis(command)
command += generate_payload_redis("quit")
gopher_payload = f"gopher://{ip}:6379/_{urllib.parse.quote(urllib.parse.quote(command))}"
return gopher_payload
if __name__ == "__main__":
ip = sys.argv[1]
command = sys.argv[2]
print(generate_payload(ip, command))
Gerando a payload novamente:
Recarregando a página na guia anônima:
E finalmente RCE!
Download do laboratório:
Referências
Onde praticar Hacking ?
O Hacking Club é uma plataforma de treinamento em cybersecurity, que permite você aprender hacking de forma totalmente prática.
Temos mais de 50 ambientes com vulnerabilidades reais com write-ups para você treinar e aprender hacking. Semanalmente lançamos máquinas gratuitas para você praticar e se desafiar no hacking!