Os perigos do assert em Python
Dhruv Patel
18 de agosto de 2022
0 minutos de leituraHá muitas maneiras de encontrar bugs no código Python: o depurador integrado (pdb), uma boa quantidade de testes unitários, um depurador em uma IDE como Pycharm ou Visual Studio, instruções try/catch, instruções if/else, assert statements ou a velha e conhecida prática de cobrir cada canto da sua base de código com instruções print(), como se isso nunca fosse sair de moda.
As instruções assert podem nos ajudar a encontrar bugs rapidamente e são muito menos intrusivas do que uma quantidade enorme de instruções print. No entanto, ao contrário das instruções print, as instruções assert podem ser inesperadamente arriscadas!
Este artigo explica como usar asserts com segurança e o que pode torná-los inseguros. Ao final, você saberá como usar assert da melhor forma sem se expor inadvertidamente a problemas de segurança.
Como usar instruções assert em Python
Usar assert é fácil! As instruções assert são instruções simples, o que significa que cabem em uma única linha de código Python.
Veja um exemplo simples de uma instrução assert:
Na instrução acima, você está afirmando que a expressão é avaliada como True, de forma semelhante a uma verificação booleana.
Se a expressão for False, uma exceção será lançada. Por exemplo:
Para facilitar a análise de um rastreamento de pilha, você pode especificar uma mensagem de asserção, que será incluída no AssertionError. Isso pode ser útil em cenários complexos de depuração.
A variável integrada __debug__ do Python precisa estar definida como True para que as instruções assert sejam usadas. Se estiver definida como False, as instruções assert não serão executadas durante a execução do programa. Isso é uma preocupação séria, pois executar Python no modo otimizado (algo comum em ambientes de produção, definido pela flag de linha de comando PYTHONOPTIMIZE, -O) define a variável __debug__ como False, desativando as instruções assert, o que pode introduzir vulnerabilidades inadvertidamente no seu projeto. Vamos explorar isso em mais detalhes abaixo.
Quando usado corretamente, assert é uma ferramenta poderosa para depuração. No entanto, quanto mais complexo for o uso, maior será o risco de introduzir vulnerabilidades de segurança durante o desenvolvimento.
Resumo — As instruções assert são muito úteis para depurar aplicações Python, mas não devem ser usadas para o tratamento normal de erros nem para controlar o fluxo do programa, pois podem:
Interromper a aplicação ao lançar uma exceção, o que pode causar indisponibilidade em produção.
Causar efeitos colaterais inesperados (inclusive vulnerabilidades) na aplicação quando o modo de depuração está desativado e o runtime do Python ignora as instruções assert sem avisar.
Como usar assert com segurança
Vamos explorar como usar instruções de asserção para depurar um programa, analisando uma aplicação de exemplo que calcula juros simples.
Primeiro, instale uma versão estável do Python. Em seguida, abra uma pasta e crie um arquivo Python chamado safe_assert_example.py. Você pode usar qualquer editor de código compatível.
Adicione o código a seguir ao arquivo safe_assert_example.py:
Este programa define a função simpleInterest e calcula juros simples usando o capital, o tempo e a taxa.
Para calcular os juros corretamente, você precisa informar números inteiros positivos para o capital, o tempo e a taxa. Este exemplo usa instruções assert para verificar se os valores p, t e r são números inteiros positivos. Se um valor não for um número inteiro positivo, o programa retornará uma mensagem de erro informativa.
Hora de testar! Execute o programa com o comando a seguir:
Você deverá ver a saída abaixo, com juros simples de 1.2:
Como você pode ver, o programa é executado sem problemas e não retorna uma mensagem de erro.
Agora, veja o que acontece quando você tenta executar o programa com uma entrada inválida. Altere o valor de t de 5 para a string "time":
Agora, execute o programa novamente com este comando:
Desta vez, você deverá ver o seguinte erro:
Essa saída inclui um AssertionError. Essa exceção mostra que a instrução assert funciona como esperado, pois detectou o argumento inválido da função.
Neste exemplo, a asserção nos ajudou a depurar o programa e interrompeu a execução assim que falhou.
Lembre-se de que executar o programa no modo otimizado (definindo a flag de linha de comando PYTHONOPTIMIZE, -O) desativa as instruções assert sem avisar. Agora, veja como esse comportamento afeta a aplicação.
Mantendo "`time=" como valor de t, execute o programa de juros simples no modo otimizado com o comando a seguir:
O resultado será:
Observe que, embora o programa tenha um argumento de função inválido, a saída acima não inclui o AssertionError esperado. Em vez disso, o Python exibe um TypeError, pois você tentou multiplicar variáveis do tipo inteiro e string. Isso acontece porque as instruções assert não foram executadas e indica que devemos considerar alternativas, como:
Adicionar dicas de tipo para ajudar a detectar chamadas incorretas a
simpleInterestantes da execução do código.Usar instruções
ifem vez de asserções para verificar tipos durante a execução.
Como usar assert de forma insegura
Assert deve ser usado apenas para testes e depuração — não em ambientes de produção. Como as instruções assert só são executadas quando a variável __debug__ está definida como True, o interpretador Python pode desativá-las.
Como demonstrado no exemplo acima, executar Python no modo otimizado usando -O impede que as instruções assert funcionem, pois isso define __debug__ como False.
Por isso, não é seguro usar instruções assert para nada que precise ser executado em produção. Por exemplo, nunca use asserts para validar tokens ou entradas de usuários. Essas instruções não serão executadas em produção, então tokens ou entradas de usuários não serão validados, o que pode introduzir vulnerabilidades na sua aplicação.
Para destacar a gravidade de usar assert para validação em um ambiente de produção, considere uma função de autorização de usuários que recebe uma lista de usuários e permite que eles avancem com base no nível de acesso. Primeiro, o programa usa assert para verificar se a lista não é nula e se é uma instância de lista. Em seguida, de acordo com a função, o administrador recebe acesso completo à aplicação. Se houver uma função de administrador na lista de usuários, nenhum erro de asserção será exibido. Mas, se não houver uma função de administrador na lista, será lançado um erro.
Em seguida, cole este código em um arquivo chamado unsafe_assert_example.py:
A função authorize_admin_user verifica as funções do usuário. Se ele for administrador, a função indicará que temos acesso completo à aplicação. Em geral, as funções do usuário são carregadas do banco de dados; se uma delas for de administrador, o usuário recebe acesso completo ao app. Caso contrário, a asserção falhará e lançará uma exceção.
A última linha do arquivo chama a função authorize_admin_user, passando uma lista que contém duas funções: admin e user.
Como esperado, o usuário recebe acesso completo à aplicação.
Vamos chamar a função de usuário sem incluir a função de administrador na lista. Altere a última linha do código em unsafe_assert_example.py para:
Em seguida, execute o programa e observe a saída:
Como esperado, a função lança um AssertionError porque admin não estava na lista de funções.
Agora, execute o mesmo programa com a saída otimizada:
A saída será igual à imagem abaixo. Embora não houvesse uma função admin na lista, a autorização de administrador foi bem-sucedida porque a instrução assert não foi executada.
Além disso, alterar a chamada da função para usar uma lista de números inteiros nessa mesma execução com saída otimizada ainda demonstraria que assert deve ser usado apenas para depuração, e não para validação.
Altere a chamada da função para authorize_admin_user([1,2]) e execute-a com o mesmo comando: python -O unsafe_assert_example.py.
A saída do programa continua a mesma.
No exemplo da calculadora de juros simples, as consequências foram pequenas quando as instruções assert não foram executadas. Embora não tenhamos recebido a mensagem de erro desejada, pudemos usar a mensagem de erro do Python para entender o que aconteceu. Mais importante ainda, não introduzimos vulnerabilidades, pois usamos as instruções assert para uma depuração segura.
Neste exemplo, usamos assert de forma insegura e deixamos nossa aplicação Python vulnerável ao conceder acesso de administrador a um usuário que não era administrador.
Se quiser testar esses exemplos mais a fundo, você pode encontrar todo o código deste artigo neste repositório.
O que fazer e o que evitar ao usar assert
Assert ajuda desenvolvedores a depurar código Python com facilidade e a escrever menos linhas de código. No entanto, se não for usado com segurança, pode introduzir problemas de segurança nas suas aplicações.
Desativar asserts em um ambiente de produção pode ter consequências devastadoras. Essa prática pode introduzir várias portas dos fundos e pontos de interrupção na aplicação, que agentes mal-intencionados podem explorar.
A principal função de um assert deve ser isolar a causa raiz de um bug; ele nunca deve ser usado para o tratamento padrão de erros. Embora asserts possam ser úteis, é essencial usá-los para depuração e testes, e não para controlar o fluxo lógico do programa ou da aplicação. Use-os apenas em situações seguras. Se você procura uma alternativa ao assert, testes unitários, depuradores, condicionais e validadores são opções melhores e mais seguras.
Agora que você sabe como usar assert com segurança, leia o guia rápido de boas práticas em Python para saber mais sobre como manter suas aplicações seguras em todas as plataformas e ambientes.
Mais recursos de desenvolvimento e segurança em Python
Você também pode experimentar nossa ferramenta gratuita online para verificar código Python e ver como o mecanismo do Snyk Code analisa seu código em busca de problemas de segurança e qualidade.
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.
