Solução do CTF Fetch the Flag 2022: Roadrunner
Giovanni Funchal
10 de novembro de 2022
0 minutos de leituraObrigado 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.

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.

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.

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

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.

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

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.

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.

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?

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.

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:

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.
