L'évaluation d'une expression mathématique dans une chaîne de caractères
stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Renvoie le message d'erreur suivant:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Je sais que eval
pouvez contourner ce problème, mais n'est-ce pas là une meilleure et surtout la plus sûre méthode pour évaluer une expression mathématique qui est stockée dans une chaîne de caractères?
- ^ est l'opérateur XOR. La valeur attendue est de 6. Vous voulez probablement pow(2,4).
- ou plus pythonically 2**4
- Si vous ne voulez pas utiliser la fonction eval, alors la seule solution est la mise en place de la grammaire de l'analyseur. Jetez un oeil à pyparsing.
Vous devez vous connecter pour publier un commentaire.
Pyparsing peut être utilisé pour analyser des expressions mathématiques. En particulier, fourFn.py
montre comment analyser de base des expressions arithmétiques. Ci-dessous, j'ai rewrapped fourFn en numérique analyseur de classe pour en faciliter la réutilisation.
Vous pouvez l'utiliser comme cette
eval
est malRemarque: même si vous utilisez set
__builtins__
àNone
cela pourrait quand même être possible de sortir à l'aide de l'introspection:Évaluer une expression arithmétique à l'aide de
ast
Vous pouvez facilement limiter la plage de valeurs autorisées pour chaque opération ou de tout résultat intermédiaire, par exemple, de limiter les arguments d'entrée pour
a**b
:Ou de limiter l'ampleur des résultats intermédiaires:
Exemple
import math
?ast.dump(ast.parse(python_exression, mode='eval'))
de sortie et d'étendreeval_()
pour gérerast.Call
type. Remarque: si quelque chose peut être fait; cela ne veut pas dire qu'il doit être fait.ast.parse
n'est pas sûr. Par exempleast.parse('()' * 1000000, '<string>', 'single')
une panne de l'interprète.if len(expr) > 10000: raise ValueError
.len(expr)
vérifier? Ou, votre point est que il y a des bugs en Python de la mise en œuvre et, par conséquent, il est impossible d'écrire le code de sécurité en général?Certaines des solutions de rechange
eval()
etsympy.sympify().evalf()
*:*SymPy
sympify
est aussi dangereuse selon l'avertissement suivant à partir de la documentation.Ok, donc le problème avec eval est qu'il peut échapper à son sandbox trop facilement, même si vous vous débarrasser de
__builtins__
. Toutes les méthodes pour échapper à la sandbox de descendre à l'aide degetattr
ouobject.__getattribute__
(via le.
opérateur) pour obtenir une référence à un objet dangereux par le biais de certains permis d'objet (''.__class__.__bases__[0].__subclasses__
ou similaire).getattr
est éliminé par la mise en__builtins__
àNone
.object.__getattribute__
est difficile, puisqu'il ne peut tout simplement être retiré, à la fois parce queobject
est immuable et parce que la suppression il allait tout casser. Cependant,__getattribute__
est accessible uniquement via le.
opérateur, afin de purge que de votre apport est suffisant pour assurer eval ne peut échapper à son bac à sable.Dans le traitement des formules, la seule valide l'utilisation d'une virgule, c'est quand il est précédé ou suivi par
[0-9]
, afin que nous venons de supprimer toutes les autres instances de.
.Note que bien que python normalement traite
1 + 1.
comme1 + 1.0
, cela permettra de supprimer la fin.
et de vous laisser avec1 + 1
. Vous pouvez ajouter)
,, et
EOF
à la liste des choses a permis de suivre.
, mais pourquoi s'embêter?La raison
eval
etexec
sont si dangereux, c'est que la valeur par défautcompile
fonction génère du bytecode pour être valable, toute expression python, et la valeur par défauteval
ouexec
exécutera toute python bytecode. Toutes les réponses à ce jour ont porté sur le fait de restreindre le bytecode qui peuvent être générés (par désinfection d'entrée) ou la construction de votre propre nom de domaine spécifique de la langue à l'aide de l'AST.Au lieu de cela, vous pouvez facilement créer un simple
eval
fonction qui est incapable de faire quelque chose infâme et peut facilement l'exécution des contrôles sur la mémoire ou le temps utilisé. Bien sûr, si il est simple de mathématiques, qu'il y a un raccourci.La façon dont cela fonctionne est simple, toute constante mathématique de l'expression en toute sécurité est évalué lors de la compilation et stockée comme une constante. Le code de l'objet retourné par la compilation se compose de
d
, qui est le pseudo-code d'LOAD_CONST
, suivi du numéro de la constante de charge (généralement le dernier de la liste), suivie parS
, qui est le pseudo-code d'RETURN_VALUE
. Si ce raccourci ne fonctionne pas, cela signifie que la saisie de l'utilisateur n'est pas une expression constante (contient une variable ou d'un appel de fonction ou similaire).Cela ouvre également la porte à certains plus sophistiqués formats d'entrée. Par exemple:
Cela nécessite effectivement d'évaluer le bytecode, qui est encore assez simple. Python bytecode est une pile langage orienté, donc tout est une simple question de
TOS=stack.pop(); op(TOS); stack.put(TOS)
ou similaire. La clé est de ne mettre en œuvre les opcodes qui sont sûrs (chargement/stockage des valeurs, des opérations mathématiques, le retour des valeurs) et non dangereux (à l'attribut de la recherche). Si vous voulez que l'utilisateur soit en mesure d'appeler des fonctions (l'ensemble de raison de ne pas utiliser le raccourci ci-dessus), vous pouvez simplement faire de votre mise en œuvre deCALL_FUNCTION
autoriser uniquement les fonctions dans un "coffre-fort" la liste.Évidemment, la vraie version de ce serait un peu plus (il y a 119 opcodes, 24 mathématiques liée). L'ajout de
STORE_FAST
et quelques autres permettrait d'entrée comme'x=5;return x+x
ou similaire, trivialement facilement. Il peut même être utilisé pour exécuter créé par l'utilisateur, les fonctions, aussi longtemps que l'utilisateur a créé fonctions sont eux-mêmes exécutés par VMeval (ne les faites pas appelable!!! ou ils pourraient obtenir utilisé comme un rappel de quelque part). Boucles de gestion exige le soutien de lagoto
bytecode, qui signifie passer d'unefor
itérateur pourwhile
et le maintien d'un pointeur à l'instruction, mais n'est-ce pas trop difficile. Pour la résistance à DOS, la boucle principale doit vérifier combien de temps a passé depuis le début du calcul, et certains opérateurs devraient refuser l'entrée sur certains la limite du raisonnable (BINARY_POWER
être le plus évident).Bien que cette approche est un peu plus long qu'un simple grammaire de l'analyseur pour les expressions simples (voir ci-dessus à propos de vous contenter de prendre l'compilé constante), elle s'étend facilement à plus compliqué d'entrée, et ne nécessite pas de traiter avec la grammaire (
compile
prendre quoi que ce soit arbitrairement complexe et le réduit à une suite d'instructions simples).C'est un massivement réponse tardive, mais je pense utile pour référence future. Plutôt que de rédiger votre propre calcul de l'analyseur (bien que le pyparsing exemple ci-dessus est grand) vous pouvez utiliser SymPy. Je n'ai pas beaucoup d'expérience avec elle, mais il contient beaucoup plus puissant de mathématiques moteur que n'importe qui est susceptible d'écrire pour une application spécifique et la base de l'évaluation de l'expression est très facile:
Très cool en effet! Un
from sympy import *
apporte beaucoup plus de fonction de soutien, comme les fonctions trigonométriques, les fonctions spéciales, etc., mais j'ai évité qu'ici à montrer ce qui est à venir à partir d'où.evalf
ne pas prendre numpy ndarrays.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
ce appelssubprocess.Popen()
où j'ai passéls
au lieu derm -rf /
. L'index sera probablement différent sur d'autres ordinateurs. C'est une variante de la Ned Batchelder exploiterVous pouvez utiliser le module ast et d'écrire un NodeVisitor qui vérifie que le type de chaque nœud est partie d'une liste blanche.
Car il fonctionne par l'intermédiaire d'une liste blanche plutôt que d'une liste noire, c'est sûr. Les seules fonctions et de variables, il peut accéder à sont celles que vous avez explicitement donner accès. J'ai rempli un dict avec les mathématiques, les fonctions liées de sorte que vous pouvez facilement fournir l'accès à ces si vous voulez, mais vous devez explicitement l'utiliser.
Si la chaîne tente d'appeler des fonctions qui n'ont pas été fournis, ou appeler l'une des méthodes, une exception sera levée, et il ne sera pas exécuté.
Car il utilise Python intégré dans l'analyseur et l'évaluateur, il hérite également Python de préséance et les règles de la promotion en tant que bien.
Le code ci-dessus a été testé uniquement sur Python 3.
Si vous le souhaitez, vous pouvez ajouter un délai d'attente décorateur sur cette fonction.
Je pense que je voudrais utiliser
eval()
, mais faudrait d'abord assurez-vous que la chaîne est valide expression mathématique, par opposition à quelque chose de malveillant. Vous pouvez utiliser une regex pour la validation.eval()
prend également des arguments supplémentaires que vous pouvez utiliser pour restreindre l'espace de noms il fonctionne pour plus de sécurité.+
,-
,*
,/
,**
,(
,)
ou quelque chose de plus compliquéeval()
si vous n'avez pas de contrôle d'entrée même si vous limitez l'espace de noms par exemple,eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
CPU, de la mémoire.[Je sais c'est une vieille question, mais il est intéressant de souligner de nouvelles solutions comme ils pop up]
Depuis python3.6, cette capacité est maintenant construit dans la langue, inventé "f-chaînes".
Voir: PEP 498 -- Chaîne Littérale de l'Interpolation
Par exemple (notez le
f
préfixe):Voici ma solution pour le problème sans l'aide de la fonction eval. Fonctionne avec Python2 et Python3. Il ne fonctionne pas avec les nombres négatifs.
test.py
solution.py
Utilisation
eval
dans un endroit propre espace de noms:Le nettoyage de l'espace de noms doit empêcher l'injection. Par exemple:
Sinon, vous obtenez:
Vous pourriez donner l'accès au module math:
__builtins__.*
est inutile.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})
exécute le shell bourne...This is not safe
- eh bien, je pense qu'il est tout aussi sûr que l'utilisation de bash dans l'ensemble. BTW:eval('math.sqrt(2.0)')
<- "les mathématiques." est nécessaire, comme l'écrit ci-dessus.