stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Isso retorna o seguinte erro:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Eu sei que isso eval
pode contornar isso, mas não existe um método melhor e - mais importante - mais seguro para avaliar uma expressão matemática que está sendo armazenada em uma string?
Respostas:
Pyparsing pode ser usado para analisar expressões matemáticas. Em particular, fourFn.py mostra como analisar expressões aritméticas básicas. Abaixo, reenviei fourFn em uma classe de analisador numérico para facilitar a reutilização.
Você pode usar assim
fonte
eval
é mauObservação: mesmo se você usar definido
__builtins__
paraNone
, ainda pode ser possível quebrar usando introspecção:Avalie a expressão aritmética usando
ast
Você pode facilmente limitar o intervalo permitido para cada operação ou qualquer resultado intermediário, por exemplo, para limitar os argumentos de entrada para
a**b
:Ou para limitar a magnitude dos resultados intermediários:
Exemplo
fonte
import math
?ast.parse
não é seguro. Por exemplo,ast.parse('()' * 1000000, '<string>', 'single')
trava o interpretador.if len(expr) > 10000: raise ValueError
.len(expr)
cheque? Ou seu ponto é que existem bugs na implementação do Python e, portanto, é impossível escrever código seguro em geral?Algumas alternativas mais seguras para
eval()
e * :sympy.sympify().evalf()
* SymPy
sympify
também não é seguro de acordo com o seguinte aviso da documentação.fonte
Ok, então o problema com eval é que ele pode escapar de sua sandbox com muita facilidade, mesmo se você se livrar dela
__builtins__
. Todos os métodos para escapar da sandbox se resumem em usargetattr
ouobject.__getattribute__
(por meio do.
operador) para obter uma referência a algum objeto perigoso por meio de algum objeto permitido (''.__class__.__bases__[0].__subclasses__
ou semelhante).getattr
é eliminado configurando__builtins__
paraNone
.object.__getattribute__
é o difícil, pois não pode simplesmente ser removido, tanto porqueobject
é imutável quanto porque removê-lo quebraria tudo. No entanto,__getattribute__
só é acessível por meio do.
operador, portanto, purgá-lo de sua entrada é suficiente para garantir que eval não escape de sua sandbox.Em fórmulas de processamento, o único uso válido de um decimal é quando ele é precedido ou seguido por
[0-9]
, então apenas removemos todas as outras instâncias de.
.Observe que, embora o python normalmente trate
1 + 1.
como1 + 1.0
, isso removerá o rastro.
e deixará você com1 + 1
. Você poderia adicionar)
,e
EOF
à lista de coisas que podem ser seguidas.
, mas por que se preocupar?fonte
.
esteja correto ou não no momento, isso deixa o potencial para vulnerabilidades de segurança se versões futuras do Python introduzirem uma nova sintaxe, permitindo que objetos ou funções inseguros sejam acessados de outra forma. Esta solução já não é seguro em Python 3,6 por causa da f-cadeias, que permitem que o ataque seguinte:f"{eval('()' + chr(46) + '__class__')}"
. Uma solução baseada em whitelisting em vez de blacklisting será mais segura, mas realmente é melhor resolver este problema semeval
nada.Você pode usar o módulo ast e escrever um NodeVisitor que verifica se o tipo de cada nó faz parte de uma lista de permissões.
Como funciona por meio de uma lista de permissões em vez de uma lista negra, é seguro. As únicas funções e variáveis que ele pode acessar são aquelas às quais você explicitamente concede acesso. Preenchi um dicionário com funções relacionadas à matemática para que você possa fornecer acesso facilmente a elas, se quiser, mas é necessário usá-lo explicitamente.
Se a string tentar chamar funções que não foram fornecidas, ou invocar qualquer método, uma exceção será levantada e ela não será executada.
Como ele usa o analisador e o avaliador embutido do Python, ele também herda as regras de promoção e precedência do Python.
O código acima foi testado apenas no Python 3.
Se desejar, você pode adicionar um decorador de tempo limite nesta função.
fonte
O motivo
eval
eexec
são tão perigosos é que acompile
função padrão irá gerar bytecode para qualquer expressão Python válida, e o padrãoeval
ouexec
irá executar qualquer bytecode Python válido. Todas as respostas até agora se concentraram em restringir o bytecode que pode ser gerado (limpando a entrada) ou construindo sua própria linguagem de domínio específico usando o AST.Em vez disso, você pode criar facilmente uma
eval
função simples que é incapaz de fazer qualquer coisa nefasta e pode facilmente ter verificações de tempo de execução na memória ou no tempo usado. Claro, se for matemática simples, então existe um atalho.A maneira como isso funciona é simples, qualquer expressão matemática constante é avaliada com segurança durante a compilação e armazenada como uma constante. O objeto de código retornado por compile consiste em
d
, que é o bytecode paraLOAD_CONST
, seguido pelo número da constante a ser carregada (geralmente a última na lista), seguido porS
, que é o bytecode paraRETURN_VALUE
. Se este atalho não funcionar, significa que a entrada do usuário não é uma expressão constante (contém uma variável ou chamada de função ou semelhante).Isso também abre a porta para alguns formatos de entrada mais sofisticados. Por exemplo:
Isso requer uma avaliação real do bytecode, que ainda é bastante simples. O bytecode Python é uma linguagem orientada a pilha, então tudo é uma questão simples
TOS=stack.pop(); op(TOS); stack.put(TOS)
ou semelhante. A chave é implementar apenas os opcodes que são seguros (carregar / armazenar valores, operações matemáticas, valores de retorno) e não os inseguros (pesquisa de atributo). Se você deseja que o usuário seja capaz de chamar funções (toda a razão para não usar o atalho acima), simplesmente faça sua implementação deCALL_FUNCTION
permitir apenas funções em uma lista 'segura'.Obviamente, a versão real disso seria um pouco mais longa (há 119 opcodes, 24 dos quais relacionados à matemática). Adicionar
STORE_FAST
e alguns outros permitiria uma entrada semelhante'x=5;return x+x
ou semelhante, com uma facilidade trivial. Ele pode até mesmo ser usado para executar funções criadas pelo usuário, desde que as funções criadas pelo usuário sejam executadas via VMeval (não as torne chamáveis !!! ou elas poderiam ser usadas como um retorno de chamada em algum lugar). O tratamento de loops requer suporte para osgoto
bytecodes, o que significa mudar de umfor
iterador parawhile
e manter um ponteiro para a instrução atual, mas não é muito difícil. Para resistência ao DOS, o loop principal deve verificar quanto tempo se passou desde o início do cálculo, e certos operadores devem negar a entrada acima de algum limite razoável (BINARY_POWER
sendo o mais óbvio).Embora essa abordagem seja um pouco mais longa do que um analisador gramatical simples para expressões simples (veja acima sobre apenas pegar a constante compilada), ela se estende facilmente para entradas mais complicadas e não requer lidar com gramática (
compile
pegue qualquer coisa arbitrariamente complicada e a reduz para uma sequência de instruções simples).fonte
Acho que usaria
eval()
, mas primeiro verificaria se a string é uma expressão matemática válida, ao invés de algo malicioso. Você pode usar um regex para a validação.eval()
também aceita argumentos adicionais que você pode usar para restringir o namespace em que opera para maior segurança.fonte
+
,-
,*
,/
,**
,(
,)
ou algo mais complicadoeval()
se você não controlar a entrada, mesmo se restringir o namespace, por exemplo,eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
consome CPU, memória.Esta é uma resposta extremamente tardia, mas acho útil para referência futura. Em vez de escrever seu próprio analisador matemático (embora o exemplo de análise acima seja excelente), você pode usar o SymPy. Não tenho muita experiência com ele, mas contém um mecanismo matemático muito mais poderoso do que qualquer pessoa possa escrever para um aplicativo específico e a avaliação de expressão básica é muito fácil:
Muito legal mesmo! A
from sympy import *
traz muito mais suporte a funções, como funções trigonométricas, funções especiais etc., mas evitei isso aqui para mostrar o que está vindo de onde.fonte
evalf
não aceita ndarrays entorpecidos.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
este chamadassubprocess.Popen()
que passeils
em vez derm -rf /
. O índice provavelmente será diferente em outros computadores. Esta é uma variante da exploração[Sei que essa é uma pergunta antiga, mas vale a pena apontar novas soluções úteis à medida que aparecem]
Desde o python3.6, esse recurso agora é integrado à linguagem , denominada "f-strings" .
Veja: PEP 498 - Interpolação de String Literal
Por exemplo (observe o
f
prefixo):fonte
str(eval(...))
, então certamente não é mais seguro do queeval
.Use
eval
em um namespace limpo:O namespace limpo deve impedir a injeção. Por exemplo:
Caso contrário, você obteria:
Você pode querer dar acesso ao módulo de matemática:
fonte
eval("""[i for i in (1).__class__.__bases__[0].__subclasses__() if i.__name__.endswith('BuiltinImporter')][0]().load_module('sys').modules['sys'].modules['os'].system('/bin/sh')""", {'__builtins__': None})
executa o shell bourne ...This is not safe
- bem, acho que é tão seguro quanto usar o bash em geral. BTW:eval('math.sqrt(2.0)')
<- "matemática". é necessário conforme escrito acima.Aqui está minha solução para o problema sem usar eval. Funciona com Python2 e Python3. Não funciona com números negativos.
test.py
solution.py
fonte