Vom Umgang mit <C>exec<C> und <C>eval<C>

Python gilt als eine sehr dynamische Sprache. Das bringt manchen Zeitgenossen auf die Idee, die Anweisung exec beziehungsweise die Funktion eval zur Ausführung von Text als Code zu verwenden. Tatsächlich benötigt man exec und eval aber nur sehr selten, da Python für deren vermeintlich typische Anwendungen bessere Möglichkeiten bietet. Das ist auch gut so, denn exec und eval haben eine ganze Reihe von Nachteilen:

  • Die Übersichtlichkeit des Codes leidet.
  • Einrückungsfehler passieren leichter.
  • Die Kontrolle der Syntax findet erst zur Laufzeit statt.
  • Es schleichen sich leicht Sicherheitslücken ein.
  • Die Möglichkeiten, Code mit speziellen Programmen (dazu später mehr) zu untersuchen, werden eingeschränkt.

Eine "typische" Anwendung von exec stellt es dar, Code unter Verwendung von vorher definierten Bezeichnern zu generieren. Ein Beispiel findet sich in Listing 22. Die Funktion baue_addierer erzeugt eine andere Funktion addierer, die zu ihrem Argument n einen Offset hinzuzählt. Der Code von baue_addierer erscheint recht unübersichtlich; die Einrückungen des Codes der eingebetteten Funktionsdefinition addierer müssen Python-gemäß natürlich stimmen, jedoch muss die Einrückung "von Null aus" am linken Rand starten.

Listing 22

def baue_addierer(offset):
    # Fuer konsistente Einrueckungen sorgen.
    code = """
def addierer(n):
    return n + %d
""" % offset
    exec code
    return addierer
neuer_addierer = baue_addierer(3)
print neuer_addierer(2)  # 3 + 2 = 5

Wie Listing 23 zeigt, geht es viel einfacher, denn in Python lassen sich Funktionsdefinition beliebig verschachteln. Sie dürfen sogar ganze Klassen innerhalb von Funktionen oder Methoden erzeugen und als Resultat zurückgeben.

Listing 23

def baue_addierer(offset):
    def addierer(n):
        return n + offset
    return addierer

Die Funktion eval setzen manche Programmierer dazu ein, auf ein erst zur Laufzeit bekanntes Attribut eines Objekts zuzugreifen (Listing 24). Genau hierfür gibt es jedoch eigentlich die Funktionen getattr, setattr und delattr. Der eval-Ausdruck lässt sich daher durch getattr(obj, "wert%d" % n) ersetzen. (Bei mehreren Attributnamen, die sich wie hier nur durch Zahlen unterscheiden, sollten sie eher eine Liste beziehungsweise ein Dictionary nutzen. In diesen fungieren die Zahlen dann als Indizes beziehungsweise Schlüssel.)

Listing 24

def wert_n(obj, n):
    return eval("obj.wert%d" % n)

Für Modul-globale Werte funktionieren zwar nicht die oben angegebenen Funktionen, Sie können aber direkt das von der Funktion globals gelieferte Dictionary manipulieren. Dies zeigt Listing 25.

Listing 25

for name in u"Dies ist ein Beispiel".split():
    globals()[name] = name

Das ist äquivalent zu:

Dies = u"Dies"
ist = u"ist"
ein = u"ein"
Beispiel = u"Beispiel"

Ein weiteres Problem von exec und eval: Sie reißen leicht Sicherheitslöcher, falls beliebige Eingabedaten in auszuführenden Code gelangen. Die Abbildung 2 zeigt als Beispiel eine Eingabemaske für einen Funktionsplotter im Internet. Listing 26 enthält den zugehörigen Code. Die Schleife durchläuft eine Wertetabelle von -10 bis +10 (Multiplikation des Schleifenzählers mit 0.1), wertet die vom Anwender eingegebene Funktion für den aktuellen X-Wert aus und trägt den Punkt in eine Liste ein, aus der nach Ende der Schleife ein Diagramm entsteht.

Abbildung 2: Ein Funktionsplotter mit eingebauter Sicherheitslücke (siehe auch Listing 26).

Listing 26

def auswertung(funktion):
    for i in xrange(-100, 101):
        x = 0.1 * i
        y = eval(funktion)
        punkte.append((x, y))
    zeige_funktion(punkte)

Was aber geschieht, wenn die anzuzeigende "Funktion" nicht 2*x + 3 heißt, sondern os.system('rm -rf *')? Falls irgendwo im Modul das Modul os importiert wurde und der Funktionsplotter mit Schreibrechten auf das Verzeichnis zugreifen kann, löscht er in diesem Fall alle dort gelagerten Dateien!

Es gibt mehrere Möglichkeiten, um solche Sicherheitslücken beim Umgang mit exec und eval zu vermeiden. Zum einen sollten Sie eine erhaltene Eingabe möglichst auf gültige Werte prüfen (Listing 27). Im Code könnte gueltige_werte eine Liste oder ein Set sein. Lässt sich keine Menge gültiger Werte erzeugen, muss ein Parser her. Damit zerlegen Sie die Eingaben für eine sinnvolle Auswertung.

Listing 27

if eingabe in gueltige_werte:
    # Alles ok - exec oder eval anwendbar.
    ...
else:
    # Erzeuge Fehlermeldung oder verwende einen Vorgabwert.
    ...

Je nach gewünschten Ein- und Ausgabedaten können Parser aber recht aufwändig sein. Zum Glück gibt es verschiedene Parser-Frameworks für Python [6]. Interessanterweise findet sich im dort genannten PyParsing-Paket auch ein Parser für arithmetische Ausdrücke, wie man ihn für den Funktionsplotter aus dem Beispiel benutzen könnte.

Auch für das Auswerten des JSON-Formats [7], das oft für den Datenaustausch zwischen Browsern und Webservern zum Einsatz kommt, missbrauchen manche Programmierer gerne eval. Für JSON gibt es jedoch in der Python-Standardbibliothek das Modul json. Viele andere spezialisierte Parser finden sich im Python Package Index, PyPI [8].

Lücken vermeiden mit <C>subprocess<C>

Das Modul subprocess hilft ebenfalls beim Vermeiden von Sicherheitslücken. Bis vor einigen Jahren dient zur Ausführung von externen Kommandos in der Regel die Funktion system aus dem Modul os. Entsprechend oft finden sich entsprechende Stellen noch in älterem Code – und neuerem, bei dem sich der Autor den älteren zum Vorbild genommen hat.

Die Funktion os.system weist jedoch einen großen Nachteil auf, wie Listing 28 zeigt. Für "normale" Verzeichnisnamen ohne Leerzeichen funktioniert dieser Code. Stammt allerdings der Name von außerhalb des Programms, lässt er sich so manipulieren, dass der Code etwas Unerwünschtes tut. Lautet name zum Beispiel . ; rm -rf *, dann zeigt der Aufruf von verzeichnis nicht nur das aktuelle Verzeichnis an, sondern leert es anschließend auch.

Listing 28

import os
def verzeichnis(name):
    return os.system("ls -l %s" % name)

Zwar verhindern Sie diesen Angriff durch Einschließen des Platzhalters %s in einfache Anführungszeichen, aber eine nur wenig kompliziertere Zeichenkette umgeht auch diesen vermeintlichen Schutz. Solche Sorgen ersparen Sie sich mit dem subprocess-Modul. Der Code aus Listing 29 deckt die gleiche Funktionalität ab wie jener aus Listing 28, lässt sich aber von hinterhältigen Zeichenketten wie oben nicht verwirren. Hier geht der Inhalt von name direkt an den ls-Befehl, ohne Interpretation durch eine Shell.

Listing 29

import subprocess
def verzeichnis(name):
    return subprocess.call(["ls", "-l", name])

Im subprocess-Modul finden sich daneben auch noch sichere Varianten von os.popen und ähnlich gearteten Funktionen.

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

Kommentare

Infos zur Publikation

LU 11/2017: Server für Daheim

Digitale Ausgabe: Preis € 8,50
(inkl. 19% MwSt.)

LinuxUser erscheint monatlich und kostet 5,95 Euro (mit DVD 8,50 Euro). Weitere Infos zum Heft finden Sie auf der Homepage.

Das Jahresabo kostet ab 86,70 Euro. Details dazu finden Sie im Computec-Shop. Im Probeabo erhalten Sie zudem drei Ausgaben zum reduzierten Preis.

Bei Google Play finden Sie digitale Ausgaben für Tablet & Smartphone.

HINWEIS ZU PAYPAL: Die Zahlung ist ohne eigenes Paypal-Konto ganz einfach per Kreditkarte oder Lastschrift möglich!

Stellenmarkt

Aktuelle Fragen

Lieber Linux oder Windows- Betriebssystem?
Sina Kaul, 13.10.2017 16:17, 3 Antworten
Hallo, bis jetzt hatte ich immer nur mit
IT-Kurse
Alice Trader, 26.09.2017 11:35, 2 Antworten
Hallo liebe Community, ich brauche Hilfe und bin sehr verzweifelt. Ih bin noch sehr neu in eure...
Backup mit KUP unter Suse 42.3
Horst Schwarz, 24.09.2017 13:16, 3 Antworten
Ich möchte auch wieder unter Suse 42.3 mit Kup meine Backup durchführen. Eine Installationsmöglic...
kein foto, etc. upload möglich, wo liegt mein fehler?
kerstin brums, 17.09.2017 22:08, 5 Antworten
moin, zum erstellen einer einfachen wordpress website kann ich keine fotos uploaden. vom rechne...
Arch Linux Netzwerkkonfigurationen
Franziska Schley, 15.09.2017 18:04, 0 Antworten
Moin liebe Linux community, ich habe momentan Probleme mit der Einstellung des Lan/Wlan in Arc...