Prüfen ob Strings etwas Unerlaubtes machen

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
snafu
User
Beiträge: 6867
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hallo,

manchmal steht man vor dem Problem, was man mit beliebigen Nutzereingaben machen soll, wenn man diese danach ausführen möchte (Beispiel: konsolenbasierter Taschenrechner). Parst man sie in eine eigene Struktur oder vertraut man darauf, dass der User schon nichts Dummes anstellen wird? Letzteres ist natürlich reichlich naiv und nicht zu empfehlen.

Aus diesem Grund habe ich "Watchdog", den Wachhund, angefangen, der das in Python 2.6 dazugekommene AST-Modul nutzt, um Strings in Python-Grammatik zu übersetzen und Alarm schlägt (Exception), wenn etwas Verbotenes (z.B. der Import eines Moduls) im String vorkommt.

Das Ganze läuft so, dass man je eine Liste mit den erlaubten Namen für den Import und den Namen für `call()` übergibt. Mehr hab ich noch nicht, aber weitere sollen folgen. Wenn man von einer Art nichts übergibt, ist dementsprechend alles verboten (also z.B. jeglicher Import).

`Watchdog` kommt mit einer `Visitor`-Klasse, die jeden Knoten im AST-Baum auf die besagten Regeln hin überprüft und gegebenenfalls eine Ausnahme wirft. Sie erwartet ein `ast.Node`-Objekt, von dem aus sie startet. Zur Vereinfachung gibt es noch eine Funktion, die einen String nimmt, das AST-gerechte Parsen erledigt und anschließend den Visitor startet. Wenn alles gut geht, wird der String in seiner ursprünglichen Form zurückgegeben, ansonsten gab's ja die Exception. So sollen Konstrukte wie `eval(check_string(raw_input('Input: ')))` möglich sein.

Wie gesagt: Noch gibt es sicherlich reichlich Fälle, wo man das irgendwie umgehen kann. Ich werde das noch austesten. Hier erstmal der Code.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Vorsicht, es gibt zahlreiche Wege, solche Sperren zu umgehen. Python ist fast unbrauchbar als "sichere" Sprache für nicht-vertrauenswürdige Usereingaben. Ich weis nicht mehr genau, wie es ging, aber du kannst z.B. über Module, die per .egg ausgeliefert werden, an den zip-Importer ran kommen und so Module importieren, ohne das 'import' Statement zu benutzen. Oder du kannst mit Modulen, die mit File-Objekten arbeiten, an die File-Klasse kommen und Dateien öffnen, ohne file() oder open() zu benutzen.
Zuletzt geändert von Defnull am Sonntag 4. Oktober 2009, 19:12, insgesamt 1-mal geändert.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6867
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Kannst du ein paar Beispiele nennen?
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Es gab Versuche, derartiges zu bauen, schonmal - zb in Form von "rexec", wobei "r" hier für restricted stehen soll. Dieses Modul war sogar in der stdlib, wurde dann aber entfernt weil man festgestellt hat, da es immer wieder neue Möglichkeiten gab, unerwünschte Dinge zu tun. Eine Möglichkeit ist zb mittels "object.__subclasses__()" sich alle Unterklassen objects zu holen, so auch File - Oder zuerst umgekehrt von zb einem int Objekt auf "object" zu kommen. Dann gibt es noch eine Möglichkeit, das gi_code Attribut eines Generators zu benutzen, um Python Bytecode auszuführen (Trundle hat vor kurzem sowas hier gepostet). Irgendwer hat auch mal ein paar Dekoratoren für rexec geschrieben, die das Modul dann einfach umgangen haben, iirc. Der Punkt ist das es entsprechende Versuche hin zu einem "sicheren eval" schon gab und sie alle als unpraktikabel eingestuft wurden, auch von Guido selbst (ich hab auf comp.lang.python dazu mal ein paar Posts gesehen).

ADD: Oh, ist sogar noch in 2.6 enthalten: http://docs.python.org/library/rexec.html

Code: Alles auswählen

>>> print eval("(x for x in (5).__class__.__bases__[0].__subclasses__() if x.__name__ == 'file').next()")
<type 'file'>
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Die einzige mir bekannte Möglichkeit, Python code sicher aus zu führen, ist Jython in einer VM-Sandbox. Java bringt nämlich sehr umfassende Möglichkeiten mit, die Rechte der Programme zu beschneiden, die in der VM laufen.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6867
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Defnull hat geschrieben:Oder du kannst mit Modulen, die mit File-Objekten arbeiten, an die File-Klasse kommen und Dateien öffnen, ohne file() oder open() zu benutzen.
Auch wenn in einem Taschenrechner zusätzlich zu den besagten Sachen wie `1+1` nur das Modul `math` und die Aufrufe von `dir(math)` erlaubt sind?

@str1442:

Der Index-Lookup fällt glaub ich unter `Load`. Auch dies kann man abfangen und die ganzen `getattr()`-Klamotten bestimmt auch. Ich spreche hier wirklich von stark kastrierten Fällen wie eben einem Taschenrechner. Ein anderes Beispiel fällt mir auch gerade nicht ein. Oder doch: Eine Shell für FTP wäre noch sowas. Das wäre dann so wie in dem Beispiel mit `math`. Also ich teile ja durchaus die Bedenken, aber noch überzeugt mich das nicht. Ich werde das Skript mal ein bißchen erweitern und dann gucken, ob dir von dir genannte Code noch funktioniert. ;)
Benutzeravatar
snafu
User
Beiträge: 6867
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Code: Alles auswählen

import math
examiner = Examiner(calls=dir(math))
user_input = ('(lambda x, sin=eval: sin(x))'
              '("""__import__("os").system("touch 0wn3d")""")')
examiner.examine(user_input)
eval(user_input)
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Antworten