o bug
Acompanhando as publicações de vulnerabilidades na plataforma de bug bounty HackerOne, encontramos a vulnerabilidade 1679624, reportada pelo usuário vakzz, e que se tornou pública dia 6 de Outubro de 2022.
Como de constume, o relatório do vakzz fornece uma visão completa do bug, entretanto algo que chamou bastante atenção foi o fato da equipe de segurança do Gitlab não fornecer o ID do problema, dificultando o rastreamento da vulnerabilidade.
Após algum tempo investigando o repositório do Gitlab, finalmente encontramos a issue 371884 com o titulo Remote code execution via Github import, o que parecia bastante promissor. A issue, de acordo com um comentário, havia sido corrigida na versão 15.3.2.
O Sawyer é uma biblioteca que converte um hash em um objeto cujos métodos são baseados nas chaves do hash.
Os dois trechos de código subsequentes nos ajudam a exemplificar como o Redis se comporta quando adicionamos uma hash e um objeto Sawyer e depois recuperamos esse valor. O resultado será o seguinte:
- Hash
- Sawyer::Resource
No Gitlab o método import_repository recebe o parâmetro default_branch que vem do repositório do cliente (que é um Sawyer::Resource aninhado de dados), e o parâmetro é passado para change_head, que então chama branch_exists? e branch_names_include? e por fim, passa o valor para o redis.
Ou seja, o "default_branch" é um valor controlado pelo atacante, e é passado para o Redis.O cenário de exploração consiste em criar um repositório no GitHub com um branch chamado to_s e outro chamado bytesize, e então importar esse repositório para o GitLab.
A Correção
Como podemos ver no commit a classe Sawyer::Resource é "patcheada" para evitar que métodos já existentes sejam sobrescritos. Isso significa que o método to_s não será sobrescrito pela classe Sawyer::Resource e, portanto, nenhum dado malicioso será passado para a gem redis.
Ambiente de testes
Após entender qual versão do GitLab estava vulnerável, criamos um ambiente de testes para reproduzirmos o problema.
A Exploração
Resumidamente a exploração dessa vulnerabilidade consiste em:
O payload deve ser no formato RESP (protocolo de serialização Redis) que executa o comando escolhido. Esse payload é gerado usando uma chave de sessão aleatória do GitLab, que é usada posteriormente para forçar o GitLab a executar o payload.
2. Criar um grupo no GitLab
O grupo criado será usado para importar o repositório falso.
Servir um repositório falso no GitHub que responde a solicitações GET em /api/v3/repos/\w+/\w+ com um default_branch que substitui to_s e bytesize, por exemplo:
4. Importar o repositório falso no GitLab.
❕ As seguintes premissas precisam ser atendidas:
- Possuir um token de acesso pessoal do GitLab com permissões para criar grupos e importar repositórios. Tal token dever estar definido na variável de ambiente API_TOKEN.
- A variável de ambiente GROUP_NAME deve estar definida com o nome do grupo que será criado.
- O Ngrok deve estar rodando e apontando para o servidor falso. A variável de ambiente NGROK_URL é definida com o endereço do Ngrok. esse passo é importante para garantir que o servidor falso seja acessível a partir do servidor do GitLab e tenha um TLD válido com um certificado SSL válido [1].
O Exploit
Criamos uma prova de conceito para explorar o problema, que pode ser encontrada no nosso repositório. Para a utilização da prova de conceito, será preciso uma credencial de acesso e a execução dos seguintes comandos:
Também submetemos um módulo de exploração para o Metasploit Framework, que pode ser encontrado aqui
Notas extras
Na sua configuração padrão, o GitLab não permite importar de uma rede local, deve ser um TLD válido. Essa é a razão pela qual o Ngrok é usado para criar um servidor falso com um TLD válido e um certificado SSL válido.