Skip to main content

Solução do CTF Fetch the Flag 2022: Roadrunner

Escrito por

Giovanni Funchal

feature ctf roadrunner

10 de novembro de 2022

0 minutos de leitura

Obrigado por jogar Fetch the Flag com a gente! Parabéns aos milhares de participantes que se juntaram a nós no CTF Fetch the Flag. E um agradecimento especial aos Snykers que criaram, testaram e documentaram os desafios!

Se você gosta de salas de fuga, vai adorar o desafio Roadrunner da competição Fetch the Flag 2022 da Snyk! Neste post, vou explicar como abordei e resolvi o desafio explorando uma vulnerabilidade de validação de entrada no código, que nos permite escapar de uma sandbox.

Desafio

Você consegue correr mais que um papa-léguas? Nem pensar, José!

O desafio começa nos provocando. Será que vamos encontrar a flag?

Primeiro, recebemos acesso a uma página da web onde encontramos algo parecido com um playground de código online. Playgrounds online como esse são usados com frequência para aprender ou fazer experimentos sem precisar instalar uma toolchain de linguagem. A linguagem Go tem até um oficial.

Podemos digitar um código em Go e clicar em Executar. O código é enviado a um backend, que o executa e retorna o resultado. Em seguida, a página da web o exibe.

Também recebemos o código-fonte em Go do serviço backend que executa essa página (roadrunner.go), além do Dockerfile usado para empacotar o serviço.

Nosso primeiro passo é revisar as dicas recebidas e explorar um pouco.

Passo a passo

Explorando

A curiosidade costuma ser uma qualidade importante para resolver esses desafios. Por isso, a primeira coisa que fiz foi dar uma olhada e testar algumas coisas. Não programo em Go, então procurei online um exemplo de “hello world”, colei o código na página e cliquei em Executar. Recebi um “hello world” de volta. Até aqui, tudo certo.

Sandbox do Roadrunner exibindo código Go que imprime “hello world”, com um botão Executar e um painel de saída mostrando “hello world”

Esse exemplo de “hello world” usa a biblioteca fmt para imprimir uma mensagem na saída.

Depois, inspecionei o código-fonte da página no navegador. Notei que o botão Executar estava associado a uma ação de envio em um formulário.

Trecho de código HTML que mostra um formulário com o rótulo “Roadrunner Sand…” e um botão Executar

Em seguida, examinei o manipulador JavaScript, que envia uma requisição POST ao endpoint /run no backend e, como esperado, preenche o conteúdo da caixa de resultados com a resposta.

Código JavaScript que processa o envio de um formulário e envia dados de script para um endpoint /run usando uma requisição POST

Depois, voltei minha atenção ao código-fonte do backend, fornecido no desafio. Vamos começar pelo Dockerfile:

Código de Dockerfile que mostra argumentos de versão do Debian e do Go, comandos de instalação de pacotes e a cópia de flag.txt para o diretório da aplicação

Encontramos a flag! Podemos ver que a imagem implantada contém um arquivo flag.txt no diretório raiz. Agora só precisamos capturá-la. Temos que encontrar uma forma de ler o conteúdo desse arquivo.

Encontrando uma pista

Vamos dar uma olhada em roadrunner.go. A função main é sempre um bom ponto de partida.

Trecho de código Go mostrando um servidor Roadrunner sendo iniciado e aguardando conexões em uma porta local

Aqui vemos os dois endpoints, / e /run. Vamos começar pelo /, tratado pela função welcome.

Editor de código exibindo uma função de boas-vindas em Go que analisa e executa um template index.html.

O código usa a biblioteca Go html/template para gerar o HTML da página, lendo o arquivo index.html e interpolando atributos. Interessante.

Agora vamos ver o endpoint /run, tratado pela função runner.

Editor de código com tema escuro, exibindo um manipulador HTTP em Go que decodifica uma requisição, sanitiza e executa um script e grava o resultado.

Há várias coisas interessantes acontecendo aqui. Primeiro, o código cria uma sandbox, recebe o script de entrada da requisição e o grava em um arquivo dentro da sandbox. Em seguida, há uma etapa de sanitização do script, que tenta verificar se ele é seguro para execução e pode rejeitar a entrada. Se a entrada for aceita, o script é executado, e o resultado é capturado e retornado na resposta.

Quando analisei esse código, notei primeiro que o campo dirname da estrutura Sandbox também é decodificado do JSON da requisição e, portanto, pode ser controlado pelo usuário. Esse campo é usado para criar um caminho de gravação de arquivo, o que configura uma vulnerabilidade. Mas não sabia como explorá-la, então decidi voltar à etapa de sanitização. Vamos analisar melhor sanitizeScript.

Código Go para sanitizeScript, que bloqueia manipulação de arquivos, chamadas de sistema e importações de rede.

Esse código analisa o script de entrada e, em seguida, verifica as importações. Há uma lista de bloqueio com as bibliotecas Go mais comuns para manipulação de arquivos. Se o script de entrada importar qualquer uma delas, será rejeitado com uma mensagem bem-humorada.

Lembre-se: nosso objetivo é ler o conteúdo do arquivo /flag.txt.

Minha primeira ideia foi que essa lista talvez não fosse completa ou que houvesse outras formas de ler arquivos. Como mencionei antes, não programo em Go, então passei alguns minutos pesquisando online.

A ficha caiu

Você se lembra de ter visto antes algum outro lugar onde lemos arquivos?

Trecho de código que mostra um handler de boas-vindas em Go analisando e executando um template index.html.

Sim: é a função welcome novamente. Ela claramente lê um arquivo e usa a biblioteca de templates, que não está na lista de bloqueio do sanitizeScript. Será que podemos usá-la?

Então, consultei a documentação dessa biblioteca. Depois de criar um objeto Template, executamos o template, que grava o resultado no writer.

Documentação de código que mostra o método Go `Execute` de um `Template`, com os parâmetros writer, data e error

Precisamos converter a interface io.Writer em uma string, que depois podemos imprimir. Pesquisando mais um pouco online, descobri que poderia usar strings.Builder (vale lembrar que a biblioteca strings também não está na lista de bloqueio).

Eis a solução final:

Captura de tela com o título “Roadrunner Sandbox”, exibindo um código em Go que processa um template flag.txt e imprime o resultado renderizado.

Ao executar este trecho de código, todo o conteúdo do arquivo flag.txt é exibido na saída. Conseguimos!

Conclusão sobre Roadrunner

O que aprendemos? Usamos uma entrada contaminada, cuidadosamente construída, para explorar uma vulnerabilidade de validação de entrada em um playground de código online. Escapamos da sandbox devido a uma falha na sanitização do script. Conhecer o funcionamento interno da aplicação nos ajudou a passar da ideia à exploração rapidamente. Este era apenas um desafio de CTF. Em uma situação real, criar esse tipo de exploração talvez exigisse algumas tentativas e erros, além de suposições.

Snyk Code — uma ferramenta gratuita de teste de segurança estática de aplicações (SAST) — ajuda a encontrar e corrigir vulnerabilidades de validação de entrada no seu código.

Outras técnicas, como a segurança em camadas, também podem dificultar essa exploração. Por exemplo, a função runScript poderia executar o script como outro usuário, com permissões reduzidas. Nesse caso, a vulnerabilidade não poderia ser explorada por si só, a menos que fosse combinada com uma segunda vulnerabilidade — como uma elevação de privilégios.

Executar código não confiável com segurança em uma sandbox é notoriamente difícil. As sandboxes são projetadas para impedir que aplicações acessem outros recursos e dados. Na prática, o melhor é evitar completamente a execução de código não confiável, sempre que possível.

É isso, pessoal. Espero que tenham gostado! Quer saber como encontramos todas as outras flags? Confira nossa página de soluções do Fetch the Flag para ver como fizemos.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.