i

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.

Warnung vor Strafbarkeit Das Ausprobieren von SQL-Injection auf fremden Seiten ist strafbar nach § 202a des Strafgesetzbuches (Ausspähen von Daten):
(1) Wer unbefugt sich oder einem anderen Zugang zu Daten, die nicht für ihn bestimmt und die gegen unberechtigten Zugang besonders gesichert sind, unter Überwindung der Zugangssicherung verschafft, wird mit Freiheitsstrafe bis zu drei Jahren oder mit Geldstrafe bestraft.

... es geht noch schlimmer

Dieses Beispiel funktioniert in dieser Form nur mit MySQL.
Aber auch für SQLite lassen sich ähnliche Probleme erzeugen.

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.:

  • Beschränken der Nutzerrechte: Wenn nur SELECT-Abfragen gewünscht sind, dann sollte auch nur das SELECT-Recht eingeräumt werden.
  • Abfrage der Ergebnisse bei DML-Befehlen: Wenn ein DELETE nur einen Datensatz löschen soll und cursor.rowcount mehr als einen Datensatz liefert, dann ist etwas falsch gelaufen. In diesem Fall können mit con.rollback() statt con.commit() die Änderungen rückgängig gemacht werden.
  • Multi-Cursor sollten grundsätzlich vermieden werden: Im Beispiel oben hätten es auch einzelne Cursor getan, die jeweils auch nur einen SQL-Befehl verarbeiten können.

Suche

v
3.4.5.4
dev.inf-schule.de/datenbanksysteme/zugriff/dml/exkurs_sqlinjection
dev.inf-schule.de/3.4.5.4
dev.inf-schule.de/@/page/s8taUtLau3DQcpVQ

Rückmeldung geben