Skip to main content

Injeção de código em Python: exemplos e prevenção

Escrito por
Headshot of Lucien Chemaly

Lucien Chemaly

feature python code injection

6 de dezembro de 2023

0 minutos de leitura

À medida que o software se torna cada vez mais essencial em nossa vida profissional e pessoal, cresce proporcionalmente a necessidade de proteger informações e sistemas contra ataques maliciosos. Uma das ameaças críticas que desenvolvedores Python precisam enfrentar é o risco de injeção de código, uma forma sofisticada e muitas vezes devastadora de ataque cibernético.

A injeção de código é um problema generalizado, que vai além de linguagens de programação e plataformas. Ainda assim, sua ocorrência em aplicações Python pode ser particularmente sutil e perigosa.

Como uma das linguagens mais usadas para desenvolvimento web, análise de dados e automação, Python oferece um amplo conjunto de recursos e bibliotecas que podem ser tanto uma bênção quanto um problema. Embora permita que desenvolvedores criem sistemas robustos e eficientes, também oferece inúmeras oportunidades para agentes mal-intencionados explorarem vulnerabilidades quando as convenções de programação segura não são seguidas à risca.

O desafio de prevenir a injeção de código em Python é ainda maior devido ao crescimento e à ampla utilização de componentes e pacotes de código aberto. Esses recursos prontamente disponíveis aceleram o desenvolvimento, mas podem conter falhas de segurança ocultas que permitem ataques de injeção de código.

Neste artigo, você vai conhecer os riscos e a importância das convenções de programação segura, especialmente no que diz respeito às vulnerabilidades de injeção de código e à forma como elas aparecem em aplicações Python. Ao entender a natureza da injeção de código e adotar as práticas recomendadas de programação segura, você pode contribuir para um ecossistema digital mais seguro e proteger suas aplicações contra possíveis invasões.

O que é injeção de código?

A injeção de código é um ataque furtivo em que um código malicioso é inserido em um sistema de software, fazendo com que ele execute comandos não previstos. Ao explorar vulnerabilidades, um invasor pode injetar código nocivo e causar consequências graves, como acesso não autorizado a dados, fraude financeira ou controle total do sistema.

Essas vulnerabilidades costumam ocorrer quando uma aplicação trata incorretamente as entradas do usuário. Por exemplo, o uso inseguro de funções como eval() em Python, sem a validação adequada, pode levar à injeção de código. O mesmo pode acontecer ao criar código com base em entradas do usuário sem verificações suficientes, usar código de terceiros sem uma avaliação de segurança ou ter vulnerabilidades na configuração de frameworks web ou bancos de dados.

Entender a injeção de código é essencial para quem trabalha com desenvolvimento ou segurança de software. Para isso, vamos analisar em profundidade algumas vulnerabilidades comuns que você pode encontrar.

Vulnerabilidades comuns que levam à injeção de código

As vulnerabilidades que levam à injeção de código são uma preocupação importante no desenvolvimento de software. Entendê-las e corrigi-las é essencial para criar sistemas seguros. Nas próximas seções, você vai conhecer algumas das principais causas da injeção de código e como se proteger contra elas.

Exploração de entradas controladas pelo usuário

Quando a entrada do usuário é usada diretamente, sem validação, um invasor pode inserir código Python como entrada, e a aplicação executá-lo. Veja este exemplo:

user_input = input("Enter your username: ")
query = "SELECT * FROM users WHERE username = '" + user_input + "';"
execute_query(query)  # This can be exploited

Para explorar esse código, um invasor poderia inserir algo como ' OR '1'='1'; DROP TABLE users;. O ponto e vírgula (;) encerra a consulta original, e o código SQL seguinte se torna uma nova consulta que pode excluir a tabela users do banco de dados.

Uso inseguro de eval() e funções relacionadas

Funções como eval() podem ser exploradas quando um usuário tem permissão para inserir expressões arbitrárias, como esta:

user_input = input("Enter expression: ")
result = eval(user_input)  # Unsafe

Neste exemplo, um invasor pode inserir __import__('os').system('any arbitrary command') para executar comandos arbitrários do sistema operacional. Por exemplo, ele poderia inserir um comando como rm -rf /, que tenta excluir todos os arquivos do diretório raiz do sistema em que o código está localizado.

Falta de validação e sanitização de entradas

Sem a validação adequada, um invasor pode inserir o caminho de qualquer arquivo, e a aplicação o lê assim:

user_input = input("Enter filename: ")
with open(user_input, 'r') as file:  # Vulnerable to directory traversal
    content = file.read()

Nesse caso, um invasor pode inserir um caminho como /etc/passwd para ler arquivos confidenciais do sistema.

Riscos associados à construção dinâmica de código

Construir strings de comandos dinamicamente, sem a validação adequada, pode levar a vulnerabilidades de injeção de comandos. Veja este código:

import os

directory = input("Enter the directory to list: ")
command = f"ls {directory}"  # Vulnerable to Command Injection
os.system(command)

Neste exemplo, um invasor poderia inserir uma string de diretório como ; cat /etc/passwd para listar o conteúdo do arquivo de senhas do sistema ou, pior ainda, executar outros comandos maliciosos. O ponto e vírgula (;) permite encadear comandos, possibilitando a execução de comandos arbitrários no sistema host.

Desserialização insegura

A desserialização insegura pode ocorrer quando dados não confiáveis são desserializados sem validação ou sanitização adequadas. Um invasor pode explorar essa falha para executar código arbitrário. Veja este exemplo:

import pickle
serialized_data = input("Enter serialized data: ")
deserialized_data = pickle.loads(serialized_data.encode('latin1'))  # Unsafe deserialization

Para explorar esse código, um invasor poderia criar um objeto serializado que, ao ser desserializado, executasse código arbitrário, como iniciar um shell reverso. Esse tipo de ataque pode ocorrer em aplicações do dia a dia, como um carrinho de compras online. Imagine que você esteja usando um e que ele armazene seu carrinho em um código especial. Se o aplicativo não verificar cuidadosamente se esse código é seguro ao recarregá-lo, um invasor poderá enviar um código enganoso que parece representar um carrinho de compras, mas executa ações maliciosas em segredo. Quando o aplicativo executa esse código perigoso sem perceber, abre as portas para o invasor. Ele pode roubar seus dados, manipular o aplicativo ou até mesmo assumir o controle de todo o sistema, criando um risco significativo à segurança.

Como reduzir as vulnerabilidades de injeção de código

Entender as vulnerabilidades é o primeiro passo, mas aprender a reduzi-las com eficiência é fundamental. Veja a seguir as contramedidas para as vulnerabilidades abordadas:

Proteja as entradas controladas pelo usuário

Evite executar diretamente as entradas do usuário. Se isso for necessário, use técnicas rigorosas de lista de permissões, aceitando apenas padrões de entrada especificados:

ALLOWED_COMMANDS = ["start", "stop", "restart"]
user_input = input("Enter your command: ")
if user_input in ALLOWED_COMMANDS:
    exec(user_input)
else:
    print("Invalid command.")

Use eval() e funções relacionadas com segurança

Use alternativas mais seguras, como literal_eval(), ou evite usar eval() por completo. Se não for possível evitar o uso, sanitize as entradas:

from ast import literal_eval
user_input = input("Enter expression: ")
try:
    result = literal_eval(user_input)
except ValueError:
    print("Invalid expression.")

Implemente validação e sanitização de entradas

Use expressões regulares ou outras técnicas de validação para garantir que apenas nomes de arquivos ou caminhos válidos sejam aceitos:

import re
user_input = input("Enter filename: ")
if re.match("^[a-zA-Z0-9_\-/]+\.txt$", user_input):
    with open(user_input, 'r') as file:
        content = file.read()
else:
    print("Invalid filename.")

Por exemplo, com literal_eval(), a entrada 2 + 3 seria avaliada como o valor numérico 5, enquanto uma entrada como __import__('os').system('rm -rf /') geraria um ValueError em vez de executar o comando. Isso reduz os riscos de segurança associados ao uso de eval() e oferece uma maneira mais segura de avaliar expressões que envolvem literais.

Evite a construção dinâmica de código

Use consultas parametrizadas ou instruções preparadas para prevenir a injeção de SQL:

import sqlite3
connection = sqlite3.connect('database.db')
cursor = connection.cursor()
input_username = input("Enter username: ")
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (input_username,))

Faça a desserialização com segurança

Desserialize apenas dados confiáveis. Se precisar desserializar entradas do usuário, use formatos mais seguros, como JSON, ou faça uma validação completa antes da desserialização:

import json
serialized_data = input("Enter serialized data: ")
try:
    deserialized_data = json.loads(serialized_data)
except json.JSONDecodeError:
    print("Invalid data.")

Implemente controles de acesso rigorosos

Aplicar o princípio do menor privilégio garante que usuários e processos tenham apenas o acesso (ou as permissões) mínimo necessário para realizar suas tarefas. Isso reduz a superfície de ataque ao limitar o que invasores podem fazer caso explorem uma vulnerabilidade.

Implemente controles de acesso rigorosos para restringir o acesso não autorizado a áreas confidenciais da aplicação. Na próxima seção, você vai conhecer outras medidas que podem reforçar a segurança da sua base de código.

Convenções de programação segura em Python

Até aqui, abordamos as maneiras mais eficazes de reduzir as vulnerabilidades de injeção de código, mas escrever código seguro envolve mais do que isso. Também é preciso adotar um conjunto de práticas recomendadas e convenções que ajudem a criar aplicações robustas e seguras. Ao escrever código Python, considere o seguinte:

Use ferramentas de segurança para desenvolvedores e encontre e corrija problemas

A Snyk oferece ferramentas gratuitas de segurança para Python que ajudam a identificar e corrigir vulnerabilidades no seu código da aplicação, nas dependências de código aberto, em contêineres, nas configurações de IaC e muito mais. Você pode integrar a Snyk aos seus repositórios Git, fluxos de trabalho de CI/CD, ferramentas de linha de comando e IDEs para ajudar a identificar e corrigir vulnerabilidades.

Por exemplo, a Snyk identifica dependências de código aberto vulneráveis na sua aplicação e mostra como corrigi-las facilmente. Confira esta lista de código Python vulnerável em pacotes pip na Snyk Vulnerability Database:

Banco de dados de vulnerabilidades da Snyk mostrando uma lista de vulnerabilidades em pacotes pip, incluindo pacotes maliciosos, condições de corrida e configurações padrão inseguras.

Incorporar ferramentas como a Snyk ao seu fluxo de trabalho de desenvolvimento adiciona uma camada extra de defesa e possibilita um processo de desenvolvimento de aplicações mais robusto e seguro. A Snyk oferece artigos úteis sobre como integrar as ferramentas ao seu fluxo de trabalho:

A análise de segurança regular com a Snyk oferece muitas vantagens, como acesso a um banco de dados de vulnerabilidades atualizado e ferramentas de segurança colaborativas, desenvolvidas para o trabalho em equipe. Os benefícios vão além de proteger seu código: eles também melhoram toda a experiência de desenvolvimento. Com recursos pensados para Python e foco na prevenção da injeção de código, a Snyk transforma a maneira como você aborda a segurança na programação e se torna um recurso indispensável para criar software seguro e de alta qualidade.

Adote práticas seguras de registro de logs

Evite registrar dados confidenciais de usuários ou informações do sistema que possam ser úteis para um invasor. Use frameworks de logging compatíveis com configurações seguras.

Promova um ambiente de programação segura

Veja a seguir outras estratégias que você pode adotar para criar um ambiente de programação segura:

  • Revisões regulares de código e auditorias de segurança: garanta que especialistas em segurança revisem o código regularmente para que você possa detectar vulnerabilidades logo no início.

  • Mantenha softwares e bibliotecas atualizados: use sempre as versões mais recentes de bibliotecas e frameworks, pois elas costumam incluir correções de segurança.

  • Incentive uma mentalidade voltada à segurança: promova uma cultura em que a segurança seja uma prioridade e garanta que todos os membros da equipe recebam treinamento sobre práticas de programação segura.

Para concluir

Neste artigo, você conheceu os riscos da injeção de código em Python e por que é importante seguir práticas de programação segura. A injeção de código pode ocorrer de várias formas, mas, com o conhecimento e as ferramentas certas, como a Snyk, você pode reduzir esses riscos. Não espere que um problema comprometa seu trabalho. Inclua as ferramentas de segurança da Snyk no seu processo de desenvolvimento em Python e torne seu código mais seguro e eficiente.

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.