Entenda as vulnerabilidades de injeção de comandos em Go
14 de novembro de 2024
0 minutos de leituraDesenvolvedores Go podem precisar usar comandos do sistema em várias situações, como ao manipular imagens — processando ou redimensionando arquivos —, gerenciar recursos ou coletar métricas e logs.
Em outros casos, talvez você esteja criando um novo sistema em Go que precisa se integrar a sistemas legados. Essa integração depende da execução de comandos do sistema e do processamento da saída.
Em ambos os casos, é essencial seguir as convenções de codificação segura ao iniciar comandos do sistema, pois isso pode levar a uma vulnerabilidade de segurança conhecida como injeção de comandos.
O que é injeção de comandos?
A injeção de comandos é uma vulnerabilidade de segurança que ocorre quando um aplicativo passa dados não confiáveis fornecidos pelo usuário — como informações inseridas em um formulário web — para um shell do sistema. Essa falha permite que um invasor execute comandos arbitrários no sistema operacional host com os mesmos privilégios do usuário do aplicativo.
Em Go, a injeção de comandos geralmente envolve o uso do pacote os/exec para iniciar comandos do sistema.
Veja o exemplo de código Go a seguir:
Neste trecho de código, cmdName é extraído diretamente dos parâmetros de consulta da requisição e usado para criar um comando executado no servidor. Esse é um exemplo clássico de vulnerabilidade de injeção de comandos, pois um invasor pode manipular o valor da string de consulta cmd para executar qualquer comando que quiser, podendo comprometer o servidor.
Um invasor poderia criar uma requisição com um comando malicioso, como:
Por que a injeção de comandos é perigosa? Ela permite que invasores executem comandos arbitrários no servidor, o que pode ter consequências graves, incluindo:
Vazamento de dados: invasores podem acessar dados confidenciais armazenados no servidor. Imagine que eles acessem arquivos de configuração, como
config.toml, e outros arquivos que listam recursos e credenciais.Comprometimento do sistema: invasores podem assumir o controle do servidor, abrindo caminho para novos ataques. Isso costuma envolver movimentação lateral dentro de um sistema comprometido e ações de reconhecimento para localizar outros servidores que possam ser comprometidos.
Interrupção do serviço: invasores podem executar comandos que interrompem os serviços e causam indisponibilidade. Isso pode resultar diretamente em prejuízos financeiros e riscos para o negócio devido a um ataque de negação de serviço.
O impacto de um ataque de injeção de comandos bem-sucedido pode ser devastador, afetando não só o sistema comprometido, mas também a reputação da organização e a confiança dos clientes.
Execução vulnerável de processos e injeção de comandos em Go
Desenvolvedores Go, conhecidos pela preferência por simplicidade e desempenho, podem optar por integrar comandos do sistema para aproveitar os recursos desses utilitários. Essa abordagem permite que se concentrem na criação de aplicativos robustos sem reinventar a roda. No entanto, essa integração traz seus próprios desafios, especialmente em relação à segurança.
Para ilustrar uma situação mais realista, considere um aplicativo Go que processa arquivos de imagem usando um utilitário de linha de comando, como convert.
Vamos analisar um aplicativo Go desenvolvido para processar solicitações de redimensionamento de imagens. O aplicativo usa o framework web Gin para definir um endpoint POST, /cloudpawnery/image, que redimensiona imagens com base nas informações fornecidas pelo usuário. Esse endpoint aceita parâmetros como tenantID, fileID e fileSize na string de consulta. O parâmetro fileSize é opcional e, se não for informado, assume o valor padrão "200".
O trecho de código a seguir mostra uma implementação vulnerável em Go.
A função downloadAndResize cria uma string de comando para redimensionar a imagem usando convert e a executa com exec.CommandContext.
Como a função downloadAndResize cria uma string de comando? Ela usa a entrada fileSize fornecida pelo usuário. Essa string é então executada, o que pode permitir que um invasor injete comandos maliciosos. Para reduzir esse risco, desenvolvedores Go devem validar e higienizar todas as entradas do usuário, usar comandos parametrizados e adotar práticas de segurança que permitam executar comandos com segurança.
Como reduzir as vulnerabilidades de injeção de comandos
No desenvolvimento em Go, assim como em qualquer outra linguagem, é fundamental garantir que seu código esteja protegido contra vulnerabilidades de injeção de comandos. Esse tipo de ataque ocorre quando um invasor consegue executar comandos arbitrários no host, o que pode levar a acesso não autorizado, vazamento de dados e outros problemas graves de segurança.
Confira algumas práticas recomendadas para reduzir esses riscos.
Validação e higienização de entradas
Uma das medidas fundamentais para evitar a injeção de comandos é validar e higienizar rigorosamente as entradas. No exemplo apresentado, a função downloadAndResize cria uma string de comando usando entradas fornecidas pelo usuário, como fileSize. Se essas entradas não forem devidamente validadas, um invasor poderá injetar comandos maliciosos.
Veja como melhorar a validação de entradas:
Use uma lista de permissões: defina um conjunto de valores aceitos para entradas como
fileSize. Por exemplo, permita apenas valores numéricos dentro de um intervalo razoável.Higienize as entradas: remova ou escape caracteres potencialmente perigosos das entradas do usuário. Isso pode incluir metacaracteres de shell, como
;,|,&etc. O ideal é definir o comando diretamente no código e não permitir que entradas do usuário o controlem.Use tipos estritos: converta as entradas para o tipo de dado esperado assim que possível. Por exemplo, converta
fileSizeem um número inteiro e valide o intervalo.
Veja um exemplo de como implementar essas práticas:
Mesmo assim, há muito mais que podemos fazer para proteger o código Go contra a injeção de comandos. Continue lendo!
Use APIs ou bibliotecas seguras em vez de comandos do sistema
Outra estratégia eficaz é evitar a execução direta de comandos do sistema sempre que possível. Em vez disso, use APIs ou bibliotecas seguras que ofereçam a funcionalidade necessária sem expor seu aplicativo aos riscos de injeção de comandos.
Por exemplo, se seu aplicativo precisa manipular imagens, considere usar uma biblioteca Go, como github.com/disintegration/imaging, em vez de chamar um comando externo, como convert, da biblioteca de software ImageMagick. Essa abordagem mantém a funcionalidade no ambiente com segurança de tipos do Go, reduzindo a superfície de ataque.
Ao usar bibliotecas como imaging em Go, você elimina a necessidade de criar e executar comandos de shell, reduzindo assim o risco de injeção de comandos. Ainda assim, em algumas bibliotecas e ecossistemas de linguagens, o pacote de terceiros pode ser apenas um wrapper simples para execução de comandos. Por isso, é essencial revisar o código em busca dessas operações sensíveis.
Refatore a função vulnerável downloadAndResize para evitar injeções
No exemplo anterior, mostramos um aplicativo Go potencialmente vulnerável que usa a função exec.CommandContext para executar comandos de shell. Como observamos, essa abordagem pode levar a vulnerabilidades de injeção de comandos.
Vamos refatorar a função downloadAndResize para garantir que as entradas do usuário não levem à execução de comandos arbitrários.
Uma maneira eficaz de evitar a injeção de comandos é não criar strings de comandos de shell diretamente a partir das entradas do usuário. Em vez disso, podemos usar a função exec.Command com argumentos separados. Assim, as entradas do usuário são passadas com segurança como parâmetros, sem chamar o shell nem permitir que os usuários controlem o comando em si.
Veja uma versão refatorada da função downloadAndResize que corrige a vulnerabilidade de injeção de comandos:
Nesta refatoração, separamos o comando dos argumentos. Ao usar exec.CommandContext com argumentos separados, evitamos criar uma string de comando de shell. Assim, as entradas do usuário são tratadas como dados, e não como código executável, reduzindo significativamente o risco de injeção de comandos.
Também eliminamos a necessidade de chamar o shell. O código refatorado não invoca o shell (sh -c), um vetor comum para ataques de injeção de comandos. Em vez disso, chama diretamente o utilitário convert com os argumentos especificados.
Proteja seu código com o Snyk
O Snyk Code é uma poderosa ferramenta de análise estática que ajuda desenvolvedores a identificar e corrigir vulnerabilidades no código. Ele se integra perfeitamente ao seu IDE e ao fluxo de trabalho de desenvolvimento, fornecendo feedback em tempo real sobre possíveis problemas de segurança.
Como o Snyk ajuda a identificar vulnerabilidades de injeção de comandos em Go
No exemplo em Go apresentado, a função downloadAndResize cria um comando de shell usando uma entrada fornecida pelo usuário:
Esse código está vulnerável à injeção de comandos porque incorpora diretamente a entrada do usuário à string de comando.
E se sua equipe tivesse desenvolvedores que não conhecessem as vulnerabilidades de injeção de comandos?
Você conseguiria identificar com facilidade, durante uma revisão de código, o fluxo de chamadas da análise estática entre arquivos para encontrar essa vulnerabilidade de injeção de comandos?
É aí que o Snyk entra em ação.
Veja como a segurança de aplicativos fica mais simples com a extensão Snyk Code, que destaca em tempo real o código vulnerável no editor do VS Code:

O Snyk ajuda a identificar essas vulnerabilidades analisando seu código Go e sinalizando casos em que entradas do usuário são usadas de forma insegura em comandos de shell. O Snyk mostra commits reais de outros projetos de código aberto que corrigiram essa vulnerabilidade específica, oferecendo várias referências de código como exemplos de boas práticas.
Além disso, ao clicar na aba ISSUE OVERVIEW, você encontra mais detalhes e práticas recomendadas para prevenir a injeção de comandos. Essa aba oferece análises detalhadas e recomendações para reduzir esses riscos. Ela fica disponível diretamente no IDE enquanto você programa, para que possa aplicar as recomendações sem uma troca de contexto que custa tempo:

Para saber mais sobre como proteger seu código contra esse tipo de vulnerabilidade, instale a extensão de IDE do Snyk Code e conecte seus projetos Git para identificar e corrigir problemas de segurança nos seus aplicativos Go. É fácil e gratuito: basta se cadastrar para começar a analisar seu código em busca de vulnerabilidades.
Ferramenta gratuita para verificar código
Proteja seu código antes do próximo commit.



