stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Cela renvoie l'erreur suivante:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Je sais que cela eval
peut contourner ce problème, mais n'y a-t-il pas une méthode meilleure et, plus important encore, plus sûre pour évaluer une expression mathématique stockée dans une chaîne?
Réponses:
Pyparsing peut être utilisé pour analyser des expressions mathématiques. En particulier, fourFn.py montre comment analyser les expressions arithmétiques de base. Ci-dessous, j'ai reconditionné fourFn dans une classe d'analyseur numérique pour une réutilisation plus facile.
Vous pouvez l'utiliser comme ça
la source
eval
est malRemarque: même si vous utilisez set
__builtins__
to,None
il est toujours possible de sortir en utilisant l'introspection:Évaluer l'expression arithmétique à l'aide de
ast
Vous pouvez facilement limiter la plage autorisée pour chaque opération ou tout résultat intermédiaire, par exemple pour limiter les arguments d'entrée pour
a**b
:Ou pour limiter l'ampleur des résultats intermédiaires:
Exemple
la source
import math
?ast.parse
n'est pas sûr. Par exempleast.parse('()' * 1000000, '<string>', 'single')
bloque l'interpréteur.if len(expr) > 10000: raise ValueError
.len(expr)
chèque? Ou votre point est qu'il y a des bogues dans l'implémentation de Python et qu'il est donc impossible d'écrire du code sûr en général?Quelques alternatives plus sûres à
eval()
et * :sympy.sympify().evalf()
* SymPy
sympify
est également dangereux selon l'avertissement suivant de la documentation.la source
D'accord, le problème avec eval est qu'il peut échapper trop facilement à son bac à sable, même si vous vous en débarrassez
__builtins__
. Toutes les méthodes pour sortir du bac à sable se résument à utilisergetattr
ouobject.__getattribute__
(via l'.
opérateur) pour obtenir une référence à un objet dangereux via un objet autorisé (''.__class__.__bases__[0].__subclasses__
ou similaire).getattr
est éliminé en définissant__builtins__
surNone
.object.__getattribute__
est la plus difficile, car elle ne peut pas être simplement supprimée, à la fois parce qu'elleobject
est immuable et parce que la supprimer briserait tout. Cependant,__getattribute__
n'est accessible que via l'.
opérateur, de sorte que la purge de votre entrée est suffisante pour garantir que eval ne puisse pas sortir de son bac à sable.Dans le traitement des formules, la seule utilisation valide d'une décimale est lorsqu'elle est précédée ou suivie de
[0-9]
, nous supprimons donc simplement toutes les autres instances de.
.Notez que bien que python traite normalement
1 + 1.
comme1 + 1.0
, cela supprimera la fin.
et vous laissera avec1 + 1
. Vous pouvez ajouter)
,et
EOF
à la liste des choses autorisées à suivre.
, mais à quoi bon?la source
.
soit correct ou non pour le moment, cela laisse un potentiel de vulnérabilités de sécurité si les futures versions de Python introduisent une nouvelle syntaxe permettant d'accéder d'une autre manière aux objets ou fonctions dangereux. Cette solution est déjà dangereuse en Python 3.6 en raison de f-chaînes, qui permettent l'attaque suivante:f"{eval('()' + chr(46) + '__class__')}"
. Une solution basée sur la liste blanche plutôt que sur la liste noire sera plus sûre, mais il vaut vraiment mieux résoudre ce problème sans aucun problèmeeval
.Vous pouvez utiliser le module ast et écrire un NodeVisitor qui vérifie que le type de chaque nœud fait partie d'une liste blanche.
Parce que cela fonctionne via une liste blanche plutôt qu'une liste noire, il est sûr. Les seules fonctions et variables auxquelles il peut accéder sont celles auxquelles vous lui donnez explicitement accès. J'ai rempli un dict avec des fonctions liées aux mathématiques afin que vous puissiez facilement y accéder si vous le souhaitez, mais vous devez l'utiliser explicitement.
Si la chaîne tente d'appeler des fonctions qui n'ont pas été fournies ou d'appeler des méthodes, une exception sera déclenchée et elle ne sera pas exécutée.
Comme cela utilise l'analyseur et l'évaluateur intégrés de Python, il hérite également des règles de priorité et de promotion de Python.
Le code ci-dessus n'a été testé que sur Python 3.
Si vous le souhaitez, vous pouvez ajouter un décorateur de temporisation sur cette fonction.
la source
La raison
eval
etexec
sont si dangereuses est que lacompile
fonction par défaut générera du bytecode pour toute expression python valide, et la valeur par défauteval
ouexec
exécutera tout bytecode python valide. Toutes les réponses à ce jour se sont concentrées sur la restriction du bytecode qui peut être généré (en nettoyant l'entrée) ou sur la création de votre propre langage spécifique au domaine à l'aide de l'AST.Au lieu de cela, vous pouvez facilement créer une
eval
fonction simple qui est incapable de faire quoi que ce soit de néfaste et qui peut facilement avoir des contrôles d'exécution sur la mémoire ou le temps utilisé. Bien sûr, s'il s'agit de mathématiques simples, il existe un raccourci.La façon dont cela fonctionne est simple, toute expression mathématique constante est évaluée en toute sécurité lors de la compilation et stockée sous forme de constante. L'objet code retourné par compile se compose de
d
, qui est le bytecode pourLOAD_CONST
, suivi du numéro de la constante à charger (généralement la dernière de la liste), suivi deS
, qui est le bytecode pourRETURN_VALUE
. Si ce raccourci ne fonctionne pas, cela signifie que l'entrée utilisateur n'est pas une expression constante (contient un appel de variable ou de fonction ou similaire).Cela ouvre également la porte à certains formats d'entrée plus sophistiqués. Par exemple:
Cela nécessite en fait d'évaluer le bytecode, ce qui est encore assez simple. Le bytecode Python est un langage orienté pile, donc tout est simple
TOS=stack.pop(); op(TOS); stack.put(TOS)
ou similaire. La clé est de n'implémenter que les opcodes qui sont sûrs (chargement / stockage de valeurs, opérations mathématiques, retour de valeurs) et non dangereux (recherche d'attributs). Si vous voulez que l'utilisateur puisse appeler des fonctions (la raison pour laquelle ne pas utiliser le raccourci ci-dessus), faites simplement votre implémentation deCALL_FUNCTION
n'autoriser que les fonctions dans une liste «sûre».De toute évidence, la version réelle de cela serait un peu plus longue (il y a 119 opcodes, dont 24 sont liés aux mathématiques). L'ajout
STORE_FAST
et quelques autres permettraient des entrées similaires'x=5;return x+x
ou similaires, très facilement. Il peut même être utilisé pour exécuter des fonctions créées par l'utilisateur, à condition que les fonctions créées par l'utilisateur soient elles-mêmes exécutées via VMeval (ne les rendez pas appelables !!! ou elles pourraient être utilisées comme un rappel quelque part). La gestion des boucles nécessite la prise en charge desgoto
bytecodes, ce qui signifie passer d'unfor
itérateur àwhile
et maintenir un pointeur vers l'instruction courante, mais ce n'est pas trop difficile. Pour la résistance au DOS, la boucle principale doit vérifier le temps écoulé depuis le début du calcul, et certains opérateurs doivent refuser l'entrée au-delà d'une limite raisonnable (BINARY_POWER
étant le plus évident).Bien que cette approche soit un peu plus longue qu'un simple analyseur de grammaire pour des expressions simples (voir ci-dessus à propos de la capture de la constante compilée), elle s'étend facilement à une entrée plus compliquée, et ne nécessite pas de traiter la grammaire (
compile
prenez quelque chose de compliqué arbitrairement et le réduit à une séquence d'instructions simples).la source
Je pense que j'utiliserais
eval()
, mais je vérifierais d'abord que la chaîne est une expression mathématique valide, par opposition à quelque chose de malveillant. Vous pouvez utiliser un regex pour la validation.eval()
prend également des arguments supplémentaires que vous pouvez utiliser pour restreindre l'espace de noms dans lequel il opère pour une plus grande sécurité.la source
+
,-
,*
,/
,**
,(
,)
ou quelque chose de plus compliquéeval()
si vous ne contrôlez pas l'entrée même si vous limitez l'espace de noms, par exemple,eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
consomme du processeur, de la mémoire.C'est une réponse massivement tardive, mais je pense utile pour référence future. Plutôt que d'écrire votre propre analyseur mathématique (bien que l'exemple de pyparsing ci-dessus soit excellent), vous pouvez utiliser SymPy. Je n'ai pas beaucoup d'expérience avec cela, mais il contient un moteur mathématique beaucoup plus puissant que quiconque est susceptible d'écrire pour une application spécifique et l'évaluation des expressions de base est très simple:
Très cool en effet! A
from sympy import *
apporte beaucoup plus de support de fonctions, telles que des fonctions trigonométriques, des fonctions spéciales, etc., mais j'ai évité cela ici pour montrer ce qui vient d'où.la source
evalf
ne prend pas numpy ndarrays.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
ces appelssubprocess.Popen()
que j'ai passés à lals
placerm -rf /
. L'index sera probablement différent sur d'autres ordinateurs. Ceci est une variante de l' exploit Ned Batchelder[Je sais que c'est une vieille question, mais cela vaut la peine de signaler de nouvelles solutions utiles au fur et à mesure qu'elles apparaissent]
Depuis python3.6, cette capacité est désormais intégrée au langage , appelé "f-strings" .
Voir: PEP 498 - Interpolation de chaîne littérale
Par exemple (notez le
f
préfixe):la source
str(eval(...))
, donc ce n'est certainement pas plus sûr queeval
.Utilisation
eval
dans un espace de noms propre:L'espace de noms propre doit empêcher l'injection. Par exemple:
Sinon, vous obtiendrez:
Vous voudrez peut-être donner accès au module mathématique:
la source
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 bourne shell ...This is not safe
- eh bien, je pense que c'est aussi sûr que d'utiliser bash en général. BTW:eval('math.sqrt(2.0)')
<- "maths". est requis comme indiqué ci-dessus.Voici ma solution au problème sans utiliser eval. Fonctionne avec Python2 et Python3. Cela ne fonctionne pas avec des nombres négatifs.
test.py
solution.py
la source