Skip to main content

Injeção de comandos em Python: exemplos e prevenção

Escrito por
Headshot of Rubaiat Hossain

Rubaiat Hossain

feature python command injection

21 de dezembro de 2023

0 minutos de leitura

Apesar da reputação de simplicidade e versatilidade do Python, garantir a segurança dos programas nessa linguagem pode ser um desafio se você ou outros integrantes da equipe deixarem de seguir as boas práticas de segurança durante o desenvolvimento.

Além disso, é provável que você use bibliotecas ou outros projetos de código aberto ao criar um aplicativo Python. No entanto, esses recursos podem introduzir problemas de segurança adicionais e deixar seu programa vulnerável a exploits, como a injeção de comandos. Essa falha crítica pode ser desastrosa para seus aplicativos Python. A injeção de comandos expõe seus aplicativos à execução não autorizada de comandos, o que pode levar a violações de dados, comprometimento do sistema e outras atividades maliciosas.

Neste artigo, você vai aprender tudo sobre injeção de comandos, incluindo como essa vulnerabilidade pode surgir nos seus programas. Você também vai conhecer boas práticas de segurança para proteger seus aplicativos Python contra ataques de injeção de comandos.

O que é injeção de comandos?

A injeção de comandos ocorre quando um invasor consegue executar comandos arbitrários do sistema ao inseri-los em um programa vulnerável. Em geral, a vulnerabilidade surge quando um aplicativo envia dados de usuário não seguros (por exemplo, de formulários, cookies e cabeçalhos HTTP) para um shell do sistema.

Diversas situações podem levar a vulnerabilidades de injeção de comandos, incluindo o envio de entradas de usuário não higienizadas para comandos do sistema. Invasores podem injetar argumentos maliciosos quando um aplicativo passa diretamente a entrada do usuário para a linha de comando. Um exemplo é a vulnerabilidade de injeção de comandos encontrada em determinadas versões do MLflow (plataforma de ciclo de vida de machine learning). Essa vulnerabilidade decorre de verificações de segurança insuficientes no método predict() do arquivo backend.py.

Além disso, os aplicativos podem construir strings de comando dinamicamente com base em vários parâmetros, incluindo entradas de usuário. Isso pode ser explorado se o processo de construção não incluir verificações e validações adequadas.

Infelizmente, é fácil não entender as implicações dos comandos do sistema. O uso incorreto ou a configuração inadequada desses comandos pode introduzir vulnerabilidades de injeção de comandos com facilidade. Por exemplo, versões afetadas do PaddlePaddle, plataforma de deep learning distribuído e paralelo, são vulneráveis à injeção de comandos em fs.py por meio do método os.system.

As consequências da injeção de comandos são graves. Invasores podem obter acesso não autorizado, exfiltrar dados confidenciais ou corromper o sistema. Violações como essa podem causar prejuízos financeiros, danos à reputação e consequências jurídicas para as organizações, além de causar danos incalculáveis aos usuários cujas informações confidenciais podem ser roubadas.

Entenda e corrija a injeção de comandos em Python

A injeção de comandos continua sendo uma das ameaças mais graves à segurança de aplicativos. Para proteger aplicativos Python contra esse tipo de ataque, é essencial compreender a fundo essas vulnerabilidades e adotar medidas proativas para evitar possíveis exploits.

Vulnerabilidades comuns que levam à injeção de comandos

A seção a seguir apresenta algumas vulnerabilidades comuns que levam à injeção de comandos em Python.

Exploração de entradas controladas pelo usuário

Entradas controladas pelo usuário são dados ou valores fornecidos diretamente por ele, como dados de formulários, parâmetros de URL ou entradas pela linha de comando. Quando um aplicativo processa essas entradas sem validá-las adequadamente e as usa diretamente para executar comandos do sistema, fica vulnerável a ataques de injeção de comandos.

Veja este exemplo em Python:

# Vulnerability: Directly using user-controlled input to execute a command.
command = input("Enter a command to execute: ")
os.system(command)

Este código solicita que o usuário forneça um comando por meio do método input. Em seguida, essa entrada controlada pelo usuário é passada diretamente ao método os.system para execução, sem qualquer tipo de validação ou verificação. Isso significa que as entradas do usuário são processadas e executadas imediatamente pelo interpretador de comandos do sistema:

Terminal do Ubuntu demonstrando um teste de injeção de comandos em Python, com um comando malicioso e a listagem de diretórios

Neste exemplo simples, comandos básicos do shell são usados para testar o código. No entanto, é provável que invasores usem comandos perigosos que elevem privilégios, causem perda de dados e levem a outras situações indesejadas.

Uso inseguro de comandos do sistema e do módulo subprocess

Os comandos do sistema e o módulo subprocess do Python permitem executar comandos de shell e oferecem maneiras robustas de interagir com o sistema operacional subjacente. No entanto, se forem usados sem segurança, podem introduzir vulnerabilidades de injeção de comandos, sobretudo quando combinados com entradas controladas pelo usuário.

Veja esta vulnerabilidade:

# Vulnerability: Using subprocess with shell=True and unsanitized user input.
command = input("Enter a command for subprocess: ")
subprocess.run(command, shell=True)

Neste caso, a entrada do usuário é executada diretamente com o método subprocess.run, com shell=True ativado. O argumento shell=True faz com que o comando seja executado pelo shell do sistema, permitindo encadear comandos e realizar outras operações potencialmente maliciosas:

Terminal do Ubuntu demonstrando injeção de comandos em Python com subprocess, executando os comandos echo e ls.

Ao não validar nem higienizar a entrada do usuário, o script fica exposto a ataques de injeção de comandos e, na prática, entrega aos possíveis invasores o controle do shell do sistema.

Riscos associados à construção dinâmica de comandos

É comum que programas Python construam e executem comandos do sistema dinamicamente, especialmente quando precisam incorporar dados variáveis, como entradas de usuário. No entanto, se essa construção dinâmica não incluir medidas de segurança adequadas, abre espaço para vulnerabilidades de injeção de comandos.

Veja o trecho de código Python a seguir:

# Vulnerability: Dynamically constructing commands without sanity checks.
user_input = input("Enter a parameter: ")
command = f"echo Printing {user_input}"
os.system(command)

Este código solicita um parâmetro ao usuário e, em seguida, constrói uma string de comando que incorpora diretamente essa entrada. O comando deve exibir a entrada do usuário. No entanto, como a entrada não é validada nem higienizada antes de ser incorporada ao comando, o código fica vulnerável à injeção de comandos:

Terminal do Ubuntu mostrando uma demonstração em Python recebendo “Hello; ls -l” e exibindo “Hello” seguido de uma listagem de diretório.

Um invasor pode explorar essa vulnerabilidade inserindo um payload com separadores de comandos ou operadores de controle. Por exemplo, o usuário pode inserir Hello; ls -l, que primeiro exibirá "Hello" e depois listará o conteúdo do diretório atual, devido ao ponto e vírgula usado como separador de comandos. É um exemplo simples, mas invasores podem inserir comandos mais maliciosos, como rm -rf / ou cat /etc/shadow.

Uso inseguro de eval()

A função eval() do Python avalia dinamicamente uma string como expressão Python e retorna o resultado. Embora seja uma ferramenta poderosa, também pode ser perigosa. Veja o seguinte trecho de código Python:

# Vulnerability: Insecure use of the eval() method.
user_input = input("Enter a Python expression to evaluate: ")
try:
    result = eval(user_input)
    print(f"Result:\n{result}")
except Exception as e:
    print(f"Error:\n{e}")

Usar eval() neste código é perigoso porque a função não avalia apenas expressões matemáticas ou expressões simples do Python, mas qualquer código Python. Isso se torna ainda mais arriscado com a capacidade do Python de interagir com o sistema subjacente usando módulos como os.

Por exemplo, um invasor pode inserir:

__import__('os').popen('ls').read()

O exemplo de entrada acima importa o módulo os e, em seguida, executa o comando ls em sistemas baseados em Unix para listar o conteúdo do diretório. É uma demonstração simples, mas agentes maliciosos podem inserir comandos mais nocivos para manipular arquivos, exfiltrar dados ou até obter acesso não autorizado ao sistema:

Terminal executando demo.py com uma expressão Python que usa os.popen('ls').read(), exibindo o conteúdo do diretório.

Como mitigar vulnerabilidades de injeção de comandos

Considerando o possível impacto negativo da injeção de comandos nos seus aplicativos Python, é fundamental lidar com o problema de forma eficaz. Felizmente, você pode mitigar proativamente várias vulnerabilidades de injeção de comandos no início do ciclo de vida de desenvolvimento de software (SDLC), seguindo estas orientações.

Implemente validação e higienização adequadas das entradas

Nunca confie nas entradas dos usuários ou de fontes externas. Sempre valide as entradas de acordo com os padrões esperados e higienize-as:

import re

def sanitize_input(user_input):
    # A simple example: Allow only alphanumeric characters
    return re.sub(r'[^a-zA-Z0-9]', '', user_input)

Observe que o exemplo acima apenas remove caracteres não alfanuméricos da entrada. Embora isso impeça que invasores encadeiem comandos, em um aplicativo real você provavelmente precisará de uma higienização muito mais rigorosa, permitindo, por exemplo, apenas determinadas respostas conhecidas.

Use consultas parametrizadas e instruções preparadas

Uma consulta parametrizada é uma forma de estruturar um comando em que primeiro você define o comando e sua estrutura e, depois, fornece separadamente os parâmetros que serão inseridos nele. Assim, o comando e os dados nunca são concatenados como strings. Em vez disso, permanecem separados, e o sistema trata os dados somente como dados, não como código executável.

Uma instrução preparada leva esse conceito um passo adiante. Primeiro, a estrutura do comando é preparada; depois, você envia apenas os parâmetros para preencher os espaços reservados. Por exemplo, o módulo subprocess do Python permite executar comandos de forma que o comando fique separado dos argumentos, de maneira semelhante às consultas parametrizadas:

import subprocess

command = "ls"
directory = input("Which directory to list? ")
subprocess.run([command, directory])

Use shell=True com cautela

Ao usar o módulo subprocess do Python, evite o argumento shell=True, principalmente com entradas de usuários. Quando você executa um comando com shell=True, o shell do sistema interpreta qualquer string enviada. Se essa string contiver entradas de usuário ou for construída a partir de várias fontes sem higienização adequada, um invasor poderá injetar comandos maliciosos:

command = input("Enter the directory to list: ")
subprocess.run(f"ls {command}", shell=True)

Essa é uma receita para o desastre, pois abre caminho para acesso não autorizado ao sistema, roubo de dados, instalação de malware, elevação de privilégios e execução de comandos arbitrários. Evite usar shell=True ou, se isso for indispensável, higienize primeiro as entradas.

Evite passar entradas de usuário para métodos os

Passar entradas de usuário diretamente para métodos os, como os.spawn ou similares, deixa seu aplicativo Python vulnerável à injeção de comandos. Por isso, use APIs alternativas do Python quando houver uma opção adequada ao seu caso de uso. Se precisar de um método os, tente usá-lo sem entradas de usuário. Se isso não for possível, higienize tudo e use técnicas de codificação sensíveis ao contexto.

Limite privilégios e implemente controles de acesso rigorosos

Sempre execute seu aplicativo com o menor nível de privilégio necessário. Se um invasor explorar uma vulnerabilidade, isso limitará os danos que poderá causar. Além disso, considere isolar seu aplicativo usando ferramentas ou técnicas como chroot, contêineres ou namespaces de usuário.

Práticas de codificação segura em Python

As seções a seguir apresentam práticas de codificação segura que, quando implementadas, podem ajudar a proteger seus aplicativos Python contra vulnerabilidades de injeção de comandos.

Valide e higienize as entradas corretamente

A validação e a higienização das entradas são essenciais para proteger seus aplicativos Python contra injeção de comandos. Ao validar rigorosamente e higienizar corretamente as entradas de usuário, você reduz a superfície de ataque, impede a execução não autorizada de comandos e aumenta a segurança geral do seu software.

Veja o seguinte código:

def get_username():
    username = input("Enter your username: ")
    if len(username) < 3 or not username.isalnum():
        raise ValueError("Invalid username!")
    return username

Esta função garante que a entrada tenha o comprimento especificado e contenha apenas caracteres alfanuméricos antes de ser processada. Em um aplicativo real, provavelmente será necessário usar regras de validação mais rigorosas do que as deste exemplo simples.

Use o módulo subprocess e funções semelhantes com cuidado

O módulo subprocess pode iniciar novos processos, conectar-se aos fluxos de entrada, saída e erro deles e obter os códigos de retorno. Por isso, é essencial usar esse módulo com segurança.

Evite usar shell=True com o módulo subprocess, a menos que seja necessário, pois isso pode executar comandos em um shell e levar a ataques de injeção de comandos:

# Avoid
subprocess.run("ls -l", shell=True)

# Instead Use
subprocess.run(["ls", "-l"])

Trate com segurança as entradas de usuário e os comandos externos

Nunca concatene nem interpole entradas de usuário diretamente nas strings de comando. Sempre use listas de argumentos:

user_input = input("Enter a directory name: ")

# Avoid
subprocess.run(f"ls {user_input}", shell=True)

# Prefer
subprocess.run(["ls", user_input])

Use ferramentas de SAST e SCA para encontrar e corrigir problemas de segurança

As ferramentas de teste de segurança de aplicativos estáticos (SAST) e de análise de composição de software (SCA) ajudam você a analisar tanto o código que está escrevendo quanto os componentes de código aberto usados pelo seu software, identificando vulnerabilidades logo no início do ciclo de desenvolvimento.

A plataforma Snyk facilita ao máximo encontrar e corrigir problemas de segurança graças à nossa abordagem de IA híbrida, que identifica e corrige vulnerabilidades no seu código e oferece feedback em tempo real sobre possíveis riscos de segurança, além de recomendações para corrigi-los. O uso de ferramentas de SAST e SCA como a Snyk (que oferece um plano gratuito para sempre) ajuda você a monitorar continuamente a base de código em busca de problemas de segurança e corrigi-los logo no início, antes que cheguem à produção. Assim, podemos ajudar a prevenir:

  • A exposição a pacotes maliciosos que podem comprometer seu sistema ou seus dados

  • Ataques à cadeia de suprimentos, nos quais um componente confiável é comprometido e afeta todos os softwares que dependem dele

  • Dependências desatualizadas que podem conter vulnerabilidades sem correção disponível

De modo geral, incorporar ferramentas de SAST e SCA como a Snyk ao seu processo de desenvolvimento garante que você não esteja apenas criando softwares funcionais, mas também se protegendo contra o cenário em constante evolução das ameaças cibernéticas.

Práticas recomendadas para o desenvolvimento seguro em Python

Nesta seção, você vai conhecer hábitos de programação e fluxos de trabalho que pode adotar para promover o desenvolvimento seguro em Python em todas as equipes.

Faça revisões de código e auditorias de segurança regularmente

Revisões regulares de código, incluindo análises minuciosas da validação de entradas, dos mecanismos de autenticação e do tratamento de dados confidenciais, ajudam a identificar possíveis vulnerabilidades logo no início do ciclo de desenvolvimento. Ao envolver integrantes da equipe com experiência em segurança, você pode detectar problemas que talvez passem despercebidos durante o desenvolvimento inicial, como práticas de programação inseguras, configurações incorretas e entradas não sanitizadas.

Realizar auditorias de segurança periódicas é essencial para garantir que sua base de código continue protegida contra ameaças em evolução. Você pode usar ferramentas como OpenVAS, ZAP e Metasploit para testar seu programa em busca de vulnerabilidades.

Mantenha softwares e bibliotecas atualizados

Softwares e bibliotecas desatualizados costumam conter vulnerabilidades de segurança conhecidas. Atualize continuamente seu interpretador Python, frameworks e bibliotecas de terceiros para as versões mais recentes. Use gerenciadores de pacotes como pip e arquivos de configuração como requirements.txt para gerenciar e atualizar dependências com eficiência.

Use ferramentas e frameworks de segurança

Você pode usar ferramentas e frameworks de segurança desenvolvidos para Python para automatizar a detecção de vulnerabilidades. Ferramentas como a Snyk ajudam a identificar problemas no código, configurações incorretas de segurança e vulnerabilidades em pacotes de terceiros.

Encontre e corrija vulnerabilidades em Python

Proteja seus aplicativos com a análise de vulnerabilidades da Snyk e recomendações de correção. 

Não é necessário informar cartão de crédito.

Ou cadastre-se com Azure AD Docker ID Bitbucket

Ao usar a Snyk, você concorda em cumprir nossas políticas, incluindo os Termos de Serviço e a Política de Privacidade.

Incentive uma mentalidade voltada à segurança

Promover uma cultura de conscientização sobre segurança entre os desenvolvedores é essencial. Procure oferecer treinamentos, workshops e recursos de segurança para capacitar os desenvolvedores. Quando a segurança é uma prioridade desde o início, a qualidade geral do software melhora e o risco de vulnerabilidades diminui.

Um ótimo recurso para saber mais sobre como construir culturas organizacionais voltadas à segurança é o Secure Developer Podcast, que traz várias entrevistas com líderes de segurança de empresas e explora a fundo como eles estruturam seus programas de desenvolvimento.

Extensões de IDE para detecção de vulnerabilidades em tempo real são ferramentas valiosas para identificar código vulnerável enquanto você o escreve. Essas extensões se integram diretamente ao seu ambiente de desenvolvimento e destacam possíveis problemas de segurança em tempo real:

O Visual Studio Code exibe código Python com alertas do Snyk sobre injeção de comandos causada por entrada de usuário não sanitizada.

Ao detectar vulnerabilidades durante o desenvolvimento, você economiza tempo e esforço que, de outra forma, seriam gastos mais adiante no ciclo de desenvolvimento, quando o custo para corrigi-las seria muito maior.

Conclusão 

A injeção de comandos é um problema grave que afeta muitas aplicações Python. Neste guia, você aprendeu como essa vulnerabilidade pode se manifestar e quais são os riscos associados. Ao reconhecer padrões de código vulneráveis, você pode entender melhor as possíveis ameaças e as contramedidas necessárias.

Você também aprendeu convenções de programação segura, como validar entradas e usar com cuidado os módulos de sistema integrados usados para executar comandos. Ao implementar essas estratégias, você pode reforçar a segurança das suas aplicações Python, impedir possíveis ataques e contribuir para a resiliência geral dos seus softwares.

Para reforçar ainda mais seu processo de desenvolvimento, considere usar ferramentas como a Snyk. As extensões de IDE da Snyk oferecem feedback de segurança em tempo real, analisando seu código enquanto você escreve e sugerindo correções imediatas. É como ter um especialista em segurança ao seu lado, ajudando você a escrever código seguro e resiliente desde o início.

Comece a proteger seus aplicativos Python

Encontre e corrija vulnerabilidades em Python gratuitamente com o Snyk.

Não é necessário informar cartão de crédito.

Ou cadastre-se com Azure AD Docker ID Bitbucket

Ao usar o Snyk, você concorda em cumprir nossas políticas, incluindo os Termos de Serviço e a Política de Privacidade.

Leia mais

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

Blog

Por que agentes de programação com IA continuam criando falhas de controle de acesso

Agentes de programação com IA podem gerar uma lógica de autorização que compila e passa pela revisão, mas permite que um tenant acesse os dados de outro. Saiba por que é difícil detectar falhas de controle de acesso e como evitá-las.