Solução do CTF Fetch the Flag 2022: Moongoose
Jason Lynch
10 de novembro de 2022
0 minutos de leituraObrigado por participar do Fetch com a gente! Parabéns aos milhares de jogadores que participaram do CTF Fetch the Flag. E um agradecimento especial aos Snykers que criaram, testaram e documentaram os desafios!
Como funcionário da Snyk, tive a oportunidade de participar de um "teste beta" da competição Fetch the Flag 2022 da Snyk com meus colegas de equipe. Nos divertimos muito resolvendo esses desafios juntos e, pessoalmente, aprendi bastante com a experiência. Este é um passo a passo de um desafio de que gostei bastante: Moongoose.
Desafio
Se você acreditava que colocaram um ganso na Lua
Este desafio começa em uma página de login enigmática, com tema de Lua e ganso. Para chegar à flag, precisamos explorar várias vulnerabilidades: path traversal, injeção NoSQL e alguns comportamentos peculiares do JavaScript. Como em qualquer outro desafio de CTF, nosso primeiro passo é encontrar um ponto de entrada.
Passo a passo
Encontrando um ponto de entrada
Talvez você tenha percebido que o nome deste desafio, "Moongoose", está a apenas uma letra de "Mongoose" — nome de um popular framework do Node.js para MongoDB. Será que isso pode ser uma pista para a solução? Vamos ver se conseguimos confirmar essa suspeita.
Primeiro, vamos acessar o desafio e observar alguns detalhes iniciais. Encontramos:
Um formulário de login
Um formulário de cadastro
Duas imagens giratórias e divertidas: uma da Lua e outra de um ganso
Um fundo bacana com um campo de estrelas
Qualquer elemento que aceite dados do usuário é um bom ponto de partida para a análise. Por isso, vamos começar pelos formulários de login e cadastro.

Ao testar algumas combinações diferentes de nomes de usuário e senhas em cada formulário, recebemos duas mensagens de erro distintas:
Quando os campos de nome de usuário e senha estão preenchidos, recebemos:
{ "☾ 𓅬": "invalid moongoose!" }Quando um ou ambos os campos estão vazios, recebemos:
{"☾ 𓅬":"invalid moongoose: geese have credentials"}
Curiosamente, o comportamento é igual nos formulários de login e cadastro. Vamos abrir o painel de rede nas ferramentas de desenvolvedor do navegador para entender melhor o que está acontecendo.

Estou usando o Firefox nestas capturas de tela, mas os mesmos princípios se aplicam a todos os principais navegadores.
Ao observar o tráfego de rede, vemos que os dois formulários fazem a mesma requisição POST ao endpoint api/auth. Portanto, não faz diferença qual deles usamos. Um dos cabeçalhos de resposta dessas requisições chama a atenção:
Se você não conhece esse nome, Express é um framework de servidor web muito usado no Node.js. Para nós, na posição de atacantes, essa é uma informação potencialmente valiosa. Agora sabemos — ou pelo menos suspeitamos fortemente — qual é a linguagem, o ambiente de execução e o framework usados neste servidor. Isso não confirma que o servidor usa Mongoose e MongoDB, mas é um indício que reforça essa hipótese.
Se seguirmos essa hipótese e presumirmos que o MongoDB é usado no fluxo de login, talvez possamos tentar uma injeção de MongoDB no endpoint api/auth. Encontrei esta injeção de consulta no muito útil book.hacktricks.xyz:
Na sintaxe de consultas do MongoDB, $ne é um operador que corresponde a um campo cujo valor é diferente do valor informado. Em SQL, seria assim:
Se presumirmos que esse endpoint consulta o MongoDB em busca de um usuário com username = X e password = Y, essa injeção equivaleria a dizer: "me dê o usuário cujo nome de usuário não é nulo e cuja senha não é nula". Em teoria, essa consulta corresponderia a qualquer usuário válido. Vamos abrir o terminal e testar com cURL:
Parece que não vai ser tão fácil! Vamos voltar ao navegador e, com a aba de rede ainda aberta, procurar outros endpoints de API que possamos tentar explorar. Nesta captura de tela, recarreguei a página e filtrei o tráfego de rede para exibir as requisições que contêm api no caminho.

Vemos que a imagem do ganso é carregada por uma chamada a /api/image/goose.png. Assim como o endpoint /api/auth, essa resposta também contém o cabeçalho X-Powered-By: Express. Podemos presumir que esse mesmo servidor está sendo usado para servir arquivos estáticos. Vamos ver o que acontece se tentarmos solicitar outro arquivo, como package.json:
Essa resposta é um grande sinal de alerta. Parece que:
O servidor está tentando abrir em disco o arquivo que especificamos
O código da aplicação provavelmente está em
/app
Ainda não sabemos se esse servidor usa uma biblioteca ou código próprio para servir arquivos estáticos. Mas podemos tentar uma vulnerabilidade comum em servidores de arquivos estáticos: path traversal. A ideia desse exploit é usar ../ para solicitar um arquivo do diretório pai daquele onde as imagens estão armazenadas. Observe que precisamos codificar a barra como URL (%2f); caso contrário, nossa requisição seria interpretada como /api/package.json:
Finalmente confirmamos nossa suspeita inicial: este servidor usa a biblioteca Mongoose para MongoDB. Também vemos que o código principal do servidor está em server.js. Vamos tentar buscar esse arquivo:
Conseguimos! Vamos analisar esse servidor para descobrir como avançar até a flag.
Análise do sistema de autenticação
Estas são as partes do arquivo server.js que compõem o sistema de autenticação:
Há bastante coisa para analisar, então vou resumir os principais pontos:
O MongoDB não é usado no sistema de autenticação.
Este código ignora o
usernamee compara apenas um hash depasswordcom uma variávelADMIN_HASHdefinida diretamente no código.A senha é transformada em hash com bcrypt, que exige muito poder computacional para ser descoberta por força bruta.
Depois que a autenticação é concluída com sucesso, o servidor gera um token aleatório e o adiciona ao objeto global
SESSIONS. O token é enviado ao cliente em um cabeçalhoAuthorization. Em seguida, o cliente pode enviar esse cabeçalho aos endpoints que usam o middlewarerequireAuthentication.
Análise do endpoint da flag
Para buscar a flag, precisamos:
passar pela verificação de autenticação
informar o valor correto de
flagno corpo da requisição
Ao solicitar o arquivo models/user.model.js com nosso exploit de path traversal, vemos que Flag é um modelo do Mongoose e que a chamada Flag.find() consulta o MongoDB em busca de entradas em que name = <flag>.
O handler /api/flags não sanitiza nem valida o corpo da requisição. Portanto, parece que podemos tentar novamente a injeção de MongoDB que usamos antes, desta vez no lugar do nome da flag. Mas, primeiro, precisamos passar pelo middleware requireAuthentication().
Juntando tudo
Como mencionamos antes, é improvável que consigamos descobrir o ADMIN_HASH por força bruta em um prazo razoável. Será que podemos enganar o servidor para que pense que já estamos autenticados?
Vamos analisar a função que valida o token da sessão:
A falha nessa verificação é presumir que o objeto SESSIONS contém apenas tokens de sessão. Mas a herança por protótipo do JavaScript faz com que o objeto SESSIONS seja inicializado com algumas propriedades ocultas e não enumeráveis. Podemos listar essas propriedades executando esta instrução em um REPL do Node.js ou na aba Console das ferramentas de desenvolvedor do navegador:
Se estivermos certos, poderemos usar qualquer uma dessas propriedades como token de sessão e passar pela função checkToken(). Depois de combinar isso com nossa injeção de MongoDB, a requisição final fica assim:
A resposta a essa requisição contém todo o conteúdo da coleção flag — incluindo a flag verdadeira!
Moongoose capturado!
Este desafio demonstrou vários tipos de vulnerabilidade. Usamos path traversal para obter o código do servidor. Aproveitamos a herança por protótipo do JavaScript para burlar o sistema de autenticação. Por fim, usamos uma injeção de consulta do MongoDB para eliminar qualquer incerteza no endpoint /api/flags. Esta aplicação foi criada para ser vulnerável, mas vulnerabilidades desse tipo também acontecem no mundo real. Se você quiser aprender mais sobre diferentes tipos de vulnerabilidade, como explorá-las e como se proteger, o Snyk Learn é um excelente recurso prático.
Quer saber como encontramos todas as outras flags? Confira nossa página com as soluções do Fetch the Flag.
Comece a participar de desafios de Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.
