Exkurs - Gefahren bei Benutzereingaben

Das folgende Beispiel solltest du auf einer neuen Datenbank ausprobieren; die (vereinfachten) Strukturen und Beispiel-Datensätze kannst du hier herunterladen.

Teilschema der Fahrschul-Datenbank

Überraschendes Ergebnis einer Benutzereingabe

Mit folgendem Programmfragment (ganzes Programm hier zum Download) kann ein Fahrschüler wieder gelöscht werden. Probiere das Programm aus und lösche z.B. den Schüler "Andi Macht".

print("Löschen eines Fahrschülers")
print("==========================")

delName    =     input("Name   : ")

SQLBefehl = "DELETE FROM schueler  \
             WHERE Name = '%s'"  % (delName)

cursor = con.cursor()

try:
  cursor.execute(SQLBefehl)
  print("%i Datensätze gelöscht" % (cursor.rowcount))

except mysql.connector.Error as err:
  print("Fehler bei der SQL-Ausführung: %s" % (err))
  raise # Fehler - also Stopp der Verarbeitung

cursor.close()
con.commit()

Abgesehen davon, dass es bestimmt keine gute Idee ist, nur den Nachnamen abzufragen (es gibt sicherlich mehrere "Meier" in einer realen Datenbank), stellt sich hier noch ein weiteres Problem:
Betrachte den folgenden Dialog mit dem Programm. Wie kommt die hoffentlich überraschende Ausgabe zustande?

Meldung, dass 6 Datensätze (alle) gelöscht wurden.
Bei einer sog. SQL-Injection versucht ein Angreifer, Sicherheitslücken in der Verarbeitung von Benutzereingaben auszunutzen, um eigene Datenbankbefehle auszuführen.

Im Beispiel wurde die Einschränkung der Datensätze durch das Erweitern um OR '1'='1' ausgehebelt. Damit werden alle Datensätze gelöscht. Und noch viel schlimmer: In der Datenbank war beim Fremdschlüssel in der fahrstunde-Tabelle die Anweisung ON DELETE CASCADE hinterlegt. Damit wurden auch dort alle Datensätze gelöscht, da jetzt die Schlüsselpartner fehlen.

... es geht noch schlimmer

Nehmen wir an, es soll im Programm in einer Logtabelle mitgeschrieben werden, wenn z.B. Daten eines Schülers ausgelesen werden. Der Programmierer hat das so gelöst, dass beim SELECT-Befehl zuvor noch ein INSERT in die Logtabelle erzeugt wird. Zur Vereinfachung behandelt er diese beiden Befehle in einem Multi-Cursor, der mehrere SQL-Befehle abarbeitet.

Du kannst das selbst einmal ausprobieren. (Beispielprogramm):

cursor = con.cursor()

selName = input("Gesuchter Schüler: ")

SQLBefehle = "CREATE TABLE IF NOT EXISTS log (Zugriff DATETIME); \
              INSERT INTO log VALUES (NOW()); \
              SELECT SNR, Name, Vorname \
                FROM schueler \
               WHERE Name LIKE '%s'" % (selName)

# wir gehen mal auf Nummer sicher...
try:
  for result in cursor.execute(SQLBefehle, multi=True):
    if result.with_rows:
      row = result.fetchone()
      while row!=None:
        print(row[0],row[1],row[2])
        row = result.fetchone()

   
except mysql.connector.Error as err:
  print("Fehler bei der SQL-Ausführung: %s" % (err))
  raise # Fehler - also Stopp der Verarbeitung

cursor.close()
con.commit()

Der folgende Dialog zeigt aber ein gravierendes Problem auf:

Eingabe, mit der sogar Tabellen gelöscht werden

Hier werden gleich zwei weitere SQL-Befehle "eingeschleust". Da es sich um DDL-Kommandos handelt, werden diese auch direkt ausgeführt - auch wenn noch nachfolgend eine Fehlermeldung erzeugt wird (falsches Ende der Anweisung) und somit das commit fehlt.

Aufgabe 1

Wie müsste die SQL-Injection angepasst werden, so dass die Fehlermeldung vermieden wird?

Aufgabe 2

Schreibe eine SQL-Injection, die einen weiteren Schüler einfügt.

Abhilfen

Um SQL-Injection zu vermeiden gibt es verschiedene Maßnahmen bei der Programmierung, u.a.:

X

Fehler melden

X

Suche