Redescobrindo a injeção de argumentos ao usar ferramentas de VCS — git e mercurial
Alessio Della Libera
23 de agosto de 2022
0 minutos de leituraUm dos principais objetivos desta pesquisa foi explorar como é possível executar comandos arbitrários mesmo usando uma API segura que impede a injeção de comandos. O foco são ferramentas de sistema de controle de versão (VCS), como git e hg (mercurial), que, entre outras opções, permitem a execução de comandos arbitrários (em determinadas circunstâncias).
Os alvos desta pesquisa são aplicações web e projetos de bibliotecas (escritos em qualquer linguagem de programação) que chamam esses comandos usando uma API segura. Não incluímos projetos de CLI nem projetos que chamam esses comandos usando dados provenientes de fontes armazenadas, como arquivos de configuração.
Nesta publicação, mostramos situações comuns em que, mesmo usando uma API segura, é possível executar comandos arbitrários por meio da injeção de opções de argumentos. Também apresentamos exemplos de correção e sugestões para mantenedores de projetos de código aberto.
Contexto sobre injeção de argumentos
Considere a seguinte situação: um desenvolvedor quer chamar comandos do git ou hg no diretório do projeto para interagir com repositórios. No entanto, valores controlados pelo usuário são usados como parâmetros desses comandos (permitindo que os usuários especifiquem a URL do repositório, o nome da branch etc.). Como o desenvolvedor conhece a vulnerabilidade de injeção de comandos, decide usar uma API segura para executar comandos do shell. Por exemplo, se o projeto usa Node.js, a API spawn do módulo child_process impede a injeção de comandos quando chamada da seguinte maneira:
Mas isso nem sempre acontece. Como veremos em breve, isso também depende do comando chamado por essas APIs. Se quiser saber mais sobre injeção de argumentos, este artigo de Staaldraad é uma ótima referência.
Por que devemos nos preocupar com o git?
Alguns subcomandos do git têm opções que permitem ao usuário especificar quais programas/comandos serão executados. Um exemplo é a opção --upload-pack, disponível nos seguintes subcomandos do git:
Vamos considerar, por exemplo, os subcomandos git fetch, ls-remote e pull, que implementam a opção --upload-pack.
Dependendo do comando, há argumentos obrigatórios, seguidos de argumentos opcionais. Por exemplo, git fetch exige pelo menos um argumento, ou seja, um repositório, seguido por refspec e um conjunto de opções.
Executar os comandos a seguir em um terminal executará o comando de shell touch HELLO:
Como é o código vulnerável com “git”?
Para demonstrar essa vulnerabilidade, vou usar JavaScript e Python. Para manter os exemplos curtos, vamos supor que os valores de url e branch são controlados pelo usuário e não são sanitizados corretamente.
O exemplo a seguir mostra como ainda é possível executar código arbitrário, mesmo usando uma API segura que protege contra as formas clássicas de injeção de comandos:
JavaScript (NodeJS)
A última linha mostra que um payload comum de injeção de comandos (ou seja, um payload com caracteres especiais como ; & | , etc. para encadear comandos) não funciona.
Python
É importante observar que as situações acima podem levar à execução de comandos se os argumentos obrigatórios dos subcomandos correspondentes do git forem fornecidos. Por exemplo, se apenas um argumento for passado ao subcomando git ls-remote, sem outros argumentos, as situações anteriores não funcionarão:
JavaScript (NodeJS)
Isso acontece porque o único argumento fornecido será interpretado como um repositório (no caso de git ls-remote). Se outro argumento válido for passado ao comando, voltamos ao caso anterior e ainda podemos executar código.
Sugestões de correção
Uma possível solução para as situações anteriores é adicionar -- antes dos valores controlados pelo usuário. Como explica a documentação oficial, “ao escrever um script que deve lidar com entradas aleatórias do usuário, é uma boa prática deixar explícito quais são os argumentos, colocando -- para eliminar ambiguidades nos locais apropriados”.
Por exemplo, o código a seguir não permitirá mais a execução de comandos:
Por que devemos nos preocupar com hg (mercurial)?
Como vimos na seção anterior, algumas condições precisam ser atendidas para executar comandos arbitrários (ou seja, os argumentos obrigatórios). No entanto, esse não é o caso do hg — o que torna a exploração muito mais fácil.
Entre as várias opções disponíveis, o mercurial permite que os usuários especifiquem aliases e hooks para cada comando (clone, init, log etc.) usando a opção –config.
Como explica a documentação do mercurial, os aliases “permitem definir seus próprios comandos com base em outros comandos (ou aliases), com a opção de incluir argumentos. Um alias pode começar com um ponto de exclamação ("!") para se tornar um alias de shell. Um alias de shell é executado pelo shell, permitindo executar comandos arbitrários. Por exemplo:
isso permite usar "hg echo foo" para exibir "foo" no terminal.”
Já os hooks são “comandos ou funções Python executados automaticamente por várias ações, como iniciar ou concluir um commit”. São comandos executados quando determinadas condições são atendidas e podem ser executados antes (pre-<command>) ou depois (post-<command>) de outros comandos do mercurial.
Como é o código vulnerável ao usar “hg init”?
Veja um exemplo de como é possível executar comandos arbitrários quando o comando hg init recebe entradas do usuário sem sanitização.
Exemplo de alias
O comando touch HELLO será executado. Isso acontece porque um alias para o comando init é definido e executado no lugar do comando hg init.
O mesmo comando executado no código ficaria assim (como antes, vamos supor que source seja controlado pelo usuário e não seja sanitizado corretamente):
JavaScript (NodeJS)
Python
Exemplo de hooks
O comando touch HELLO será executado antes da execução do comando init (ou seja, pelas opções pre-<command>).
O mesmo comando executado no código ficaria assim:
JavaScript (NodeJS)
Python
Ao contrário do caso do git, para executar comandos arbitrários, basta controlar um argumento passado aos comandos do mercurial — mesmo que ele seja o único parâmetro usado.
Sugestões de correção
Mais uma vez, podemos corrigir esse problema adicionando os caracteres -- antes dos argumentos controlados pelo usuário.
Por exemplo, a exploração a seguir não funcionará mais — ela criará uma pasta de repositório com o nome igual ao conteúdo da variável source:
Resultados
Problemas semelhantes já foram explorados no passado. Alguns exemplos conhecidos são os ataques de execução remota de código contra CocoaPods Trunk e Phabricator.
A tabela abaixo resume os problemas que encontramos e divulgamos como parte desta pesquisa:
Projeto | Vulnerabilidade | Comunicado/Referência | CVE |
|---|---|---|---|
ungit | RCE | CVE-2022-25766 | |
mozilla/pontoon | RCE | N/A | |
GoCD Server | RCE | CVE-2022-29184 | |
Weblate | RCE (x2) | CVE-2022-23915 | |
simple-git | Injeção de comandos | CVE-2022-24433 | |
ruby-git | Injeção de comandos | CVE-2022-25648 | |
cocoapods-downloader | Injeção de comandos | CVE-2022-24440 | |
cocoapods-downloader | Injeção de comandos | CVE-2022-21223 | |
Masterminds/vcs | Injeção de comandos | CVE-2022-21235 | |
git-php | Injeção de comandos | CVE-2022-25866 | |
libvcs | Injeção de comandos | CVE-2022-21187 | |
workspace-tools | Injeção de comandos | CVE-2022-25865 | |
cookiecutter | Injeção de comandos | CVE-2022-24065 | |
hashicorp/go-getter | Injeção de comandos | CVE-2022-26945 |
Depois de encontrar essas vulnerabilidades, nós as comunicamos em particular aos mantenedores, seguindo nossa política de divulgação de vulnerabilidades. Quero agradecer pessoalmente a todos os mantenedores com quem entrei em contato pelo tempo dedicado e pela colaboração.
Nos estudos de caso a seguir, explicaremos como foi possível executar código remotamente e realizar injeção de comandos nesses projetos populares.
Estudo de caso: execução remota de código no Weblate
O Weblate é uma “ferramenta de localização baseada na web com integração estreita ao controle de versão”. Um usuário autenticado conseguiu executar código remotamente por meio de injeção de argumentos.
Ao adicionar novos componentes de tradução e usar repositórios mercurial, a entrada do usuário branch é usada no comando hg pull sem sanitização adequada.
O código relevante que chama o comando hg pull com valores controlados pelo usuário é:
Prova de conceito
Para executar comandos arbitrários, o usuário precisa criar um New Translation Component em um projeto de tradução, com o valor de Repository branch definido como --config=alias.pull=!id>/app/cache/static/output_rce1.txt. Nesse caso, o comando id será executado e sua saída será redirecionada para uma página disponível em http://localhost:8888/static/output_rce1.txt.
1. Faça login no aplicativo.
2. Adicione um novo projeto de tradução e clique em Save.

3. Adicione um novo componente de tradução ao projeto criado, insira o payload --config=alias.pull=!id>/app/cache/static/output_rce1.txt no campo Repository Branch e clique em Continue.

4. A saída do comando id estará disponível em http://localhost:8888/static/output_rce1.txt.

Correção
Como você pode ver, foi possível obter RCE mesmo ao lidar com repositórios git. Nesse caso, a entrada do usuário repo, sem sanitização, foi passada ao comando git ls-remote.
O mantenedor corrigiu rapidamente os dois problemas na versão 4.11.1 (consulte o Github Release). As correções para os casos de mercurial e git adicionaram os caracteres -- antes da entrada do usuário para impedir que ela fosse interpretada como argumento.
Estudo de caso: injeção de comandos no ruby-git
Ruby git é uma “biblioteca que pode ser usada para criar, ler e manipular repositórios Git por meio de chamadas de sistema ao binário git”.
Ao chamar a função fetch(remote = 'origin', opts = {}), o parâmetro remote é passado ao subcomando git fetch sem sanitização. Como vimos, git fetch é um dos subcomandos do git que aceita o argumento --upload-pack. Para saber mais sobre --upload-pack, consulte a documentação de git-fetch.
O código relevante que chama o comando git fetch com valores controlados pelo usuário é:
Prova de conceito
A PoC a seguir demonstra como foi possível executar comandos arbitrários:
Correção
O problema foi corrigido pelos mantenedores na versão 1.11.0 (versão no GitHub). Assim como no problema anterior, a correção inseriu os caracteres -- antes dos valores controlados pelo usuário.
Principais aprendizados
… para desenvolvedores
Ao executar comandos usando uma API segura — que impede a injeção de comandos e aceita valores controlados pelo usuário —, é importante garantir que esses valores não alterem o comportamento do comando por meio da injeção ou manipulação de algumas opções. Para evitar essa confusão, os caracteres -- podem ser usados para separar os argumentos dos valores controlados pelo usuário.
… para mantenedores
Se uma função ou API encapsula chamadas a comandos que aceitam valores controlados pelo usuário, vale a pena documentar esse comportamento. Assim, você evita confundir usuários que percebem que o mantenedor trata alguns problemas de sanitização (como impedir a injeção de comandos usando uma API segura), mas não outros (como quando a entrada pode ser usada como uma opção perigosa e causar um comportamento inesperado).
… para pesquisadores de segurança
Nosso foco foram os comandos git e hg, mas muitos outros funcionam de maneira semelhante. Vale lembrar que, mesmo usando APIs seguras que impedem a injeção de comandos, ainda podem surgir problemas de segurança. Tudo depende dos comandos executados. Se você encontrar problemas semelhantes (ou qualquer outra vulnerabilidade de segurança) em um projeto de código aberto apoiado pelo nosso programa, faça a denúncia pelo formulário de divulgação de vulnerabilidades da Snyk.



