Stellen Sie sicher, dass unsicherer Code nicht versehentlich verwendet wird

10

Eine Funktion f()verwendet eval()(oder etwas so Gefährliches) Daten, die ich local_fileauf dem Computer erstellt und gespeichert habe, auf dem mein Programm ausgeführt wird:

import local_file

def f(str_to_eval):
    # code....
    # .... 
    eval(str_to_eval)    
    # ....
    # ....    
    return None


a = f(local_file.some_str)

f() ist sicher zu laufen, da die Zeichenfolgen, die ich ihm zur Verfügung stelle, meine eigenen sind.

Wenn ich mich jedoch jemals dazu entscheide, es für etwas Unsicheres (z. B. Benutzereingaben) zu verwenden, können furchtbare Fehler auftreten . Wenn das nicht local_filemehr lokal ist, entsteht eine Sicherheitsanfälligkeit, da ich dem Computer vertrauen muss, der auch diese Datei bereitstellt.

Wie soll ich sicherstellen, dass ich nie "vergesse", dass die Verwendung dieser Funktion unsicher ist (es sei denn, bestimmte Kriterien sind erfüllt)?

Hinweis: eval()ist gefährlich und kann normalerweise durch etwas Sicheres ersetzt werden.

Fermi-Paradoxon
quelle
1
Normalerweise stimme ich gerne den gestellten Fragen zu, anstatt zu sagen, dass Sie dies nicht tun sollten, aber ich werde in diesem Fall eine Ausnahme machen. Ich bin mir ziemlich sicher, dass es keinen Grund gibt, warum Sie eval verwenden sollten. Diese Funktionen sind in Sprachen wie PHP und Python als eine Art Proof-of-Concept enthalten, wie viel mit interpretierten Sprachen getan werden kann. Es ist grundsätzlich unvorstellbar, dass Sie Code schreiben können, der Code erstellt, aber dass Sie nicht dieselbe Logik in eine Funktion codieren können. Python hat wahrscheinlich Rückrufe und Funktionsbindungen, mit denen Sie tun können, was Sie brauchen
user1122069
1
" Ich müsste dem Computer vertrauen, der auch diese Datei bereitstellt " - Sie müssen immer dem Computer vertrauen, auf dem Sie Ihren Code ausführen.
Bergi
Fügen Sie einen Kommentar in die Datei ein, der besagt: "Diese Zeichenfolge wird ausgewertet. Bitte geben Sie hier keinen Schadcode ein."
user253751
@immibis Ein Kommentar würde nicht ausreichen. Ich werde natürlich eine riesige "WARNUNG: Diese Funktion verwendet eval ()" in den Dokumenten der Funktion haben, aber ich würde mich lieber auf etwas viel Sichereres als das verlassen. Zum Beispiel (aufgrund der begrenzten Zeit) lese ich die Dokumente einer Funktion nicht immer erneut. Ich denke auch nicht, dass ich es sein sollte. Es gibt schnellere (dh effizientere) Wege, um zu wissen, dass etwas unsicher ist, wie die Methoden, die Murphy in seiner Antwort verknüpft hat .
Fermi Paradox
@Fermiparadox Wenn Sie eval aus der Frage herausnehmen, wird es ohne nützliches Beispiel. Sprechen Sie jetzt über eine Funktion, die aufgrund einer absichtlichen Überwachung unsicher ist, z. B. das Nicht-Entkommen von SQL-Parametern. Meinen Sie damit, dass Testcode für eine Produktionsumgebung nicht sicher ist? Wie auch immer, jetzt habe ich Ihren Kommentar zu Amons Antwort gelesen - im Grunde haben Sie gesucht, wie Sie Ihre Funktion sichern können.
user1122069

Antworten:

26

Definieren Sie einen Typ, um „sichere“ Eingaben zu dekorieren. Stellen Sie in Ihrer Funktion f()sicher, dass das Argument diesen Typ hat, oder geben Sie einen Fehler aus. Seien Sie schlau genug, um niemals eine Verknüpfung zu definieren def g(x): return f(SafeInput(x)).

Beispiel:

class SafeInput(object):
  def __init__(self, value):
    self._value = value

  def value(self):
    return self._value

def f(safe_string_to_eval):
  assert type(safe_string_to_eval) is SafeInput, "safe_string_to_eval must have type SafeInput, but was {}".format(type(safe_string_to_eval))
  ...
  eval(safe_string_to_eval.value())
  ...

a = f(SafeInput(local_file.some_str))

Dies kann zwar leicht umgangen werden, erschwert dies jedoch aus Versehen. Die Leute könnten eine solche drakonische Typprüfung kritisieren, aber sie würden den Punkt verfehlen. Da der SafeInputTyp nur ein Datentyp und keine objektorientierte Klasse ist, die in Unterklassen unterteilt werden könnte, ist die Überprüfung der Typidentität mit type(x) is SafeInputsicherer als die Überprüfung der Typkompatibilität mit isinstance(x, SafeInput). Da wir einen bestimmten Typ erzwingen möchten, ist es auch hier nicht zufriedenstellend, den expliziten Typ zu ignorieren und nur implizite Ententypisierung durchzuführen. Python hat ein Typsystem, also verwenden wir es, um mögliche Fehler zu erkennen!

amon
quelle
5
Möglicherweise ist jedoch eine kleine Änderung erforderlich. assertwird automatisch entfernt, da ich optimierten Python-Code verwende. So etwas if ... : raise NotSafeInputErrorwäre nötig.
Fermi Paradox
4
Wenn Sie diesen Weg trotzdem gehen, würde ich dringend empfehlen, die eval()Methode in eine Methode zu verschieben SafeInput, damit Sie keine explizite Typprüfung benötigen. Geben Sie der Methode einen Namen, der in keiner Ihrer anderen Klassen verwendet wird. Auf diese Weise können Sie das Ganze immer noch zu Testzwecken verspotten.
Kevin
@ Kevin: Da evalder Zugriff auf lokale Variablen möglich ist, hängt der Code möglicherweise davon ab, wie er die Variablen mutiert. Wenn Sie die Auswertung in eine andere Methode verschieben, können lokale Variablen nicht mehr mutiert werden.
Sjoerd Job Postmus
Ein weiterer Schritt könnte darin bestehen, dass der SafeInputKonstruktor einen Namen / ein Handle der lokalen Datei übernimmt und den Lesevorgang selbst durchführt, um sicherzustellen, dass die Daten tatsächlich aus einer lokalen Datei stammen.
Owen
1
Ich habe nicht viel Erfahrung mit Python, aber ist es nicht besser, eine Ausnahme zu machen? In den meisten Sprachen können Asserts deaktiviert werden und dienen (so wie ich sie verstehe) eher dem Debuggen als der Sicherheit. Ausnahmen und das Beenden der Konsole garantieren, dass der nächste Code nicht ausgeführt wird.
user1122069
11

Wie Joel einmal betont hat, lassen Sie falschen Code falsch aussehen (scrollen Sie nach unten zum Abschnitt "The Real Solution" oder lesen Sie ihn besser noch vollständig; es lohnt sich, auch wenn er Variablen anstelle von Funktionsnamen anspricht). Daher schlage ich demütig vor, Ihre Funktion in so etwas wie umzubenennen f_unsafe_for_external_use()- Sie werden höchstwahrscheinlich selbst ein Abkürzungsschema entwickeln.

Edit: Ich denke, Amons Vorschlag ist eine sehr gute, OO-basierte Variation des gleichen Themas :-)

Murphy
quelle
0

Ergänzend zum Vorschlag von @amon können Sie hier auch das Strategiemuster sowie das Methodenobjektmuster kombinieren.

class AbstractFoobarizer(object):
    def handle_cond(self, foo, bar):
        """
        Handle the event or something.
        """
        raise NotImplementedError

    def run(self):
        # do some magic
        pass

Und dann eine "normale" Implementierung

class PrintingFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        print("Something went very well regarding {} and {}".format(foo, bar))

Und eine Admin-Input-Eval-Implementierung

class AdminControllableFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        # Look up code in database/config file/...
        code = get_code_from_settings()
        eval(code)

(Hinweis: Mit einem realen Beispiel kann ich möglicherweise ein besseres Beispiel geben.)

Sjoerd Job Postmus
quelle