Desenvolvendo o exploit para o CVE-2022-2992


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 próximo passo foi olhar para os commits dessa versão, onde identificamos um commit interessante, provavelmente relacionado ao problema:





Após ler o commit, percebemos que o problema era um pouco mais complexo do que imaginávamos.

 
A Causa 

O Sawyer é uma biblioteca que converte um hash em um objeto cujos métodos são baseados nas chaves do hash.

O exemplo de código abaixo mostra como o Sawyer converte um hash em um objeto:
O problema é que a gem redis usa to_s e bytesize para gerar o comando RESP (protocolo de serialização do Redis).








Portanto, se o Sawyer::Resource for controlável por um adversário, ele pode ser usado para sobrescrever os metódos .to_s e .bytesize que posteriormente serão usado para "montar" o comando comando RESP e executar comandos no Redis.

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.

Provavelmente a forma mais fácil de criar um ambiente de testes é usando o GitLab Omnibus Docker, utilizando a seguinte imagem: gitlab/gitlab-ce:15.1.0-ce.0

Esses são os detalhes do ambiente de testes:


A Exploração

Resumidamente a exploração dessa vulnerabilidade consiste em:

1. Criar um payload

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.

Isso pode ser feito usando o script disponível aqui.

2. Criar um grupo no GitLab

O grupo criado será usado para importar o repositório falso.

3. Criar um repositório falso no GitHub

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:


{
    "default_branch": {
        "to_s": {
            "to_s": 'ggg\r\nPAYLOAD',
            "bytesize": 3,
        }
    }
}

4. Importar o repositório falso no GitLab.




Podemos confirmar que o payload foi injetado no Redis usando o seguinte comando:

5. Acionar o payload
Para acionar o payload é preciso acessar o GitLab com o cookie de sessão utilizado para gerar o payload, para forçar a execução do payload.

❕ 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:

Remote Command Execution via Github import

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.

References