stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Dies gibt den folgenden Fehler zurück:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Ich weiß, dass eval
dies umgangen werden kann, aber gibt es nicht eine bessere und - was noch wichtiger ist - sicherere Methode, um einen mathematischen Ausdruck zu bewerten, der in einer Zeichenfolge gespeichert ist?
Antworten:
Pyparsing kann verwendet werden, um mathematische Ausdrücke zu analysieren. Insbesondere zeigt fourFn.py , wie grundlegende arithmetische Ausdrücke analysiert werden. Unten habe ich fourFn zur einfacheren Wiederverwendung in eine numerische Parser-Klasse umwickelt.
Sie können es so verwenden
quelle
eval
ist böseHinweis: Selbst wenn Sie set
__builtins__
toNone
it verwenden , kann es dennoch möglich sein, mit Introspektion auszubrechen:Bewerten Sie den arithmetischen Ausdruck mit
ast
Sie können den zulässigen Bereich für jede Operation oder jedes Zwischenergebnis leicht einschränken, z. B. um Eingabeargumente für Folgendes einzuschränken
a**b
:Oder um die Größe der Zwischenergebnisse zu begrenzen:
Beispiel
quelle
import math
?ast.parse
nicht sicher ist. Zum Beispielast.parse('()' * 1000000, '<string>', 'single')
stürzt der Interpreter ab.if len(expr) > 10000: raise ValueError
. B. mit .len(expr)
Prüfung nicht behoben werden kann ? Oder ist Ihr Punkt, dass es Fehler in der Python-Implementierung gibt und es daher unmöglich ist, generell sicheren Code zu schreiben?Einige sicherere Alternativen zu
eval()
und * :sympy.sympify().evalf()
* SymPy
sympify
ist gemäß der folgenden Warnung aus der Dokumentation ebenfalls unsicher.quelle
Okay, das Problem mit eval ist, dass es zu leicht aus seiner Sandbox entkommen kann, selbst wenn Sie es loswerden
__builtins__
. Alle Methoden zum Entkommen aus der Sandbox bestehen darin,getattr
oderobject.__getattribute__
(über den.
Bediener) zu verwenden, um über ein zulässiges Objekt (''.__class__.__bases__[0].__subclasses__
oder ähnliches) einen Verweis auf ein gefährliches Objekt zu erhalten .getattr
wird durch Einstellen__builtins__
auf beseitigtNone
.object.__getattribute__
ist das Schwierige, da es nicht einfach entfernt werden kann, sowohl weilobject
es unveränderlich ist als auch weil das Entfernen alles kaputt machen würde. Der Zugriff__getattribute__
ist jedoch nur über den.
Bediener möglich, sodass das Löschen Ihrer Eingabe ausreicht, um sicherzustellen, dass eval nicht aus seiner Sandbox entweichen kann.In Verarbeitungsformeln ist die einzige gültige Verwendung einer Dezimalstelle, wenn ihr vorangestellt oder gefolgt wird
[0-9]
, also entfernen wir einfach alle anderen Instanzen von.
.Beachten Sie, dass Python normalerweise so behandelt
1 + 1.
wie1 + 1.0
, dies jedoch das Nachlaufen entfernt.
und Sie mit belässt1 + 1
. Man könnte hinzufügen)
,und
EOF
auf die Liste der Dinge zu folgen erlaubt.
, aber warum die Mühe?quelle
.
im Moment richtig ist oder nicht, besteht das Potenzial für Sicherheitslücken, wenn zukünftige Versionen von Python eine neue Syntax einführen, mit der auf unsichere Objekte oder Funktionen auf andere Weise zugegriffen werden kann. Diese Lösung ist in Python 3.6 aufgrund von F-Strings, die den folgenden Angriff ermöglichen, bereits unsicher :f"{eval('()' + chr(46) + '__class__')}"
. Eine Lösung, die eher auf Whitelisting als auf Blacklisting basiert, ist sicherer, aber es ist wirklich besser, dieses Problem ohne zu löseneval
.Sie können das ast-Modul verwenden und einen NodeVisitor schreiben, der überprüft, ob der Typ jedes Knotens Teil einer Whitelist ist.
Da es über eine Whitelist und nicht über eine Blacklist funktioniert, ist es sicher. Die einzigen Funktionen und Variablen, auf die zugegriffen werden kann, sind diejenigen, auf die Sie explizit zugreifen. Ich habe ein Diktat mit mathematischen Funktionen gefüllt, damit Sie bei Bedarf problemlos auf diese zugreifen können, aber Sie müssen es explizit verwenden.
Wenn die Zeichenfolge versucht, nicht bereitgestellte Funktionen aufzurufen oder Methoden aufzurufen, wird eine Ausnahme ausgelöst und nicht ausgeführt.
Da hierfür der in Python integrierte Parser und Evaluator verwendet wird, werden auch die Prioritäts- und Heraufstufungsregeln von Python übernommen.
Der obige Code wurde nur unter Python 3 getestet.
Falls gewünscht, können Sie dieser Funktion einen Timeout-Dekorator hinzufügen.
quelle
Der Grund
eval
undexec
die Gefahr besteht darin, dass die Standardfunktioncompile
Bytecode für jeden gültigen Python-Ausdruck generiert, und die Standardfunktioneval
oderexec
einen gültigen Python-Bytecode ausführt ausführt. Alle bisherigen Antworten konzentrierten sich auf die Einschränkung des Bytecodes, der generiert werden kann (durch Bereinigen der Eingabe) oder das Erstellen einer eigenen domänenspezifischen Sprache mithilfe des AST.Stattdessen können Sie auf einfache Weise eine einfache
eval
Funktion erstellen , die nicht in der Lage ist, etwas Schändliches zu tun, und auf einfache Weise Laufzeitprüfungen des Speichers oder der verwendeten Zeit durchführen lassen. Wenn es sich um einfache Mathematik handelt, gibt es natürlich eine Abkürzung.Die Funktionsweise ist einfach: Jeder konstante mathematische Ausdruck wird während der Kompilierung sicher ausgewertet und als Konstante gespeichert. Das von compile zurückgegebene Codeobjekt besteht aus
d
dem Bytecode fürLOAD_CONST
, gefolgt von der Nummer der zu ladenden Konstante (normalerweise die letzte in der Liste), gefolgt vonS
dem Bytecode fürRETURN_VALUE
. Wenn diese Verknüpfung nicht funktioniert, bedeutet dies, dass die Benutzereingabe kein konstanter Ausdruck ist (enthält eine Variable oder einen Funktionsaufruf oder ähnliches).Dies öffnet auch die Tür zu einigen komplexeren Eingabeformaten. Beispielsweise:
Dies erfordert die tatsächliche Auswertung des Bytecodes, was immer noch recht einfach ist. Python-Bytecode ist eine stapelorientierte Sprache, daher ist alles einfach
TOS=stack.pop(); op(TOS); stack.put(TOS)
oder ähnlich. Der Schlüssel besteht darin, nur die Opcodes zu implementieren, die sicher sind (Laden / Speichern von Werten, mathematische Operationen, Zurückgeben von Werten) und nicht unsichere (Attributsuche). Wenn Sie möchten, dass der Benutzer Funktionen aufrufen kann (der ganze Grund, die obige Verknüpfung nicht zu verwenden), können Sie einfachCALL_FUNCTION
nur zulässige Funktionen in einer "sicheren" Liste implementieren .Offensichtlich wäre die eigentliche Version etwas länger (es gibt 119 Opcodes, von denen 24 mathematisch sind). Das Hinzufügen
STORE_FAST
und ein paar andere würden eine Eingabe wie'x=5;return x+x
oder ähnlich ermöglichen, trivial einfach. Es kann sogar verwendet werden, um vom Benutzer erstellte Funktionen auszuführen, solange die vom Benutzer erstellten Funktionen selbst über VMeval ausgeführt werden (machen Sie sie nicht aufrufbar !!! oder sie könnten irgendwo als Rückruf verwendet werden). Die Behandlung von Schleifen erfordert die Unterstützung dergoto
Bytecodes, was bedeutet, dass der Wechsel von einem Wesen am offensichtlichsten ist.for
Iterator zuwhile
einem Zeiger auf den aktuellen Befehl und dieser beibehalten wird, dies ist jedoch nicht zu schwierig. Für den Widerstand gegen DOS sollte die Hauptschleife prüfen, wie viel Zeit seit Beginn der Berechnung vergangen ist, und bestimmte Operatoren sollten die Eingabe über einen angemessenen Grenzwert verweigern (BINARY_POWER
Dieser Ansatz ist zwar etwas länger als ein einfacher Grammatik-Parser für einfache Ausdrücke (siehe oben, in dem nur die kompilierte Konstante erfasst wird), erstreckt sich jedoch leicht auf kompliziertere Eingaben und erfordert keine Behandlung der Grammatik (
compile
nehmen Sie etwas willkürlich Kompliziertes und reduzieren Sie es auf eine Folge einfacher Anweisungen).quelle
Ich denke, ich würde verwenden
eval()
, würde aber zuerst überprüfen, ob die Zeichenfolge ein gültiger mathematischer Ausdruck ist, im Gegensatz zu etwas Bösartigem. Sie können einen regulären Ausdruck für die Validierung verwenden.eval()
Außerdem werden zusätzliche Argumente verwendet, mit denen Sie den Namespace, in dem es ausgeführt wird, für mehr Sicherheit einschränken können.quelle
+
,-
,*
,/
,**
,(
,)
oder etwas komplizierteval()
wenn Sie die Eingabe nicht steuern, auch wenn Sie den Namespace einschränken, z. B.eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
CPU und Speicher verbrauchen.Dies ist eine massiv verspätete Antwort, aber ich halte sie für nützlich, um später darauf zurückgreifen zu können. Anstatt Ihren eigenen Mathe-Parser zu schreiben (obwohl das obige Pyparsing-Beispiel großartig ist), können Sie SymPy verwenden. Ich habe nicht viel Erfahrung damit, aber es enthält eine viel leistungsfähigere mathematische Engine, als irgendjemand wahrscheinlich für eine bestimmte Anwendung schreibt, und die grundlegende Auswertung von Ausdrücken ist sehr einfach:
Sehr cool! A
from sympy import *
bietet viel mehr Funktionsunterstützung, wie Triggerfunktionen, Sonderfunktionen usw., aber ich habe dies hier vermieden, um zu zeigen, was von wo kommt.quelle
evalf
Nimmt auch keine numpy ndarrays.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
diese Anrufe,subprocess.Popen()
die ichls
statt übergeben haberm -rf /
. Der Index wird wahrscheinlich auf anderen Computern anders sein. Dies ist eine Variante des Ned Batchelder Exploits[Ich weiß, dass dies eine alte Frage ist, aber es lohnt sich, auf neue nützliche Lösungen hinzuweisen, wenn sie auftauchen.]
Da python3.6 wird diese Fähigkeit nun in die Sprache integriert , prägt „f-Strings“ .
Siehe: PEP 498 - Literal String Interpolation
Zum Beispiel (beachten Sie das
f
Präfix):quelle
str(eval(...))
, daher ist es sicherlich nicht sicherer alseval
.Verwendung
eval
in einem sauberen Namespace:Der saubere Namespace sollte die Injektion verhindern. Zum Beispiel:
Sonst würden Sie bekommen:
Möglicherweise möchten Sie Zugriff auf das Mathematikmodul gewähren:
quelle
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})
führt die Bourne-Shell aus ...This is not safe
- Nun, ich denke, es ist genauso sicher wie die Verwendung von Bash insgesamt. Übrigens:eval('math.sqrt(2.0)')
<- "Mathe". ist wie oben beschrieben erforderlich.Hier ist meine Lösung für das Problem ohne eval. Funktioniert mit Python2 und Python3. Es funktioniert nicht mit negativen Zahlen.
test.py
solution.py
quelle