mitmario.dev

Platzhalter, immer

Python Sandbox 4 Min Lesezeit 5 BeispieleLektion 4 von 6

Eine Suche nach einem Namen, den jemand eintippt, ist mit dem f-String aus Lektion 2.4 schnell gebaut. Bei Berger klappt das, bei O’Connor nicht mehr. Der naheliegende Weg ist hier verkehrt.

In den Abfragen der letzten Lektion standen alle Werte fest im Befehl, 'Käse' oder '2026-10-15'. Ein Programm, das man wirklich benutzt, fragt aber nach dem, was jemand eingibt. Einen Namen, ein Suchwort, einen Monat. Die Beispiele drehen sich um die Mitgliederliste des Vereins aus Abschnitt 12, diesmal in einer Datenbank.

Eine Suche mit f-String

Eine Suche mit f-String
from verein import oeffnen

verbindung = oeffnen()


def suche(nachname):
    abfrage = f"SELECT vorname, nachname, telefon FROM mitglieder WHERE nachname = '{nachname}'"
    print(abfrage)
    return verbindung.execute(abfrage).fetchall()


print(suche("Berger"))
verbindung.close()

suche() baut die Abfrage mit einem f-String zusammen und setzt den Nachnamen zwischen die Anführungszeichen. Zur Probe wird hingeschrieben, was dabei herauskommt. Bei Berger sieht der Befehl so aus, wie man ihn von Hand geschrieben hätte, und beide Bergers kommen zurück. So weit ist alles gut.

Ein Apostroph im Namen

Ein Apostroph im Namen
from verein import oeffnen

verbindung = oeffnen()


def suche(nachname):
    abfrage = f"SELECT vorname, nachname, telefon FROM mitglieder WHERE nachname = '{nachname}'"
    print(abfrage)
    return verbindung.execute(abfrage).fetchall()


print(suche("O'Connor"))
verbindung.close()

Dann sucht jemand Liam O’Connor. Der Apostroph in seinem Namen ist für SQL das Ende des Textes. Der Befehl lautet jetzt WHERE nachname = 'O'Connor'. Der Text ist nach dem O zu Ende, und mit Connor' kann SQLite nichts anfangen. Die Abfrage endet mit sqlite3.OperationalError: near "Connor": syntax error, einem Satzbaufehler in der Nähe von Connor.

Falsch geschrieben ist im Programm nichts. Es ist an einem Namen gescheitert, und Namen mit Apostroph gibt es reichlich, bei Menschen genauso wie bei der Pizzeria um die Ecke.

Ein Name, der keiner ist

Ein Name, der keiner ist
from verein import oeffnen

verbindung = oeffnen()


def suche(nachname):
    abfrage = f"SELECT vorname, nachname, telefon FROM mitglieder WHERE nachname = '{nachname}'"
    print(abfrage)
    return verbindung.execute(abfrage).fetchall()


for zeile in suche("x' OR '1'='1"):
    print(zeile)
verbindung.close()

Schlimmer wird es, wenn jemand den Apostroph mit Absicht tippt. Das Suchwort x' OR '1'='1 schließt den Text nach dem x und hängt eine Bedingung an, die immer stimmt, denn '1' ist immer gleich '1'. Heraus kommen alle Mitglieder samt Telefonnummer, obwohl niemand mit dem Nachnamen x im Verein ist. Wer das Suchwort eintippt, bestimmt jetzt mit, was in der Datenbank passiert.

Das heißt SQL-Injection (vom englischen inject, einschleusen). Es ist einer der ältesten Wege, auf denen ganze Kundenlisten aus Webseiten verschwinden. Ein kleiner Trost ist, dass execute immer nur einen Befehl ausführt. Ein angehängtes ; DROP TABLE mitglieder endet mit You can only execute one statement at a time. Fürs Ausspähen reicht aber schon, was im Beispiel passiert.

Platzhalter

Mit Platzhalter
from verein import oeffnen

verbindung = oeffnen()


def suche(nachname):
    abfrage = "SELECT vorname, nachname, telefon FROM mitglieder WHERE nachname = ?"
    return verbindung.execute(abfrage, (nachname,)).fetchall()


print(suche("Berger"))
print(suche("O'Connor"))
print(suche("x' OR '1'='1"))
verbindung.close()

Die Lösung kennst du schon vom Eintragen der Zeilen. Im Befehl steht ein Fragezeichen, und der Wert kommt in einem Tupel daneben, execute(abfrage, (nachname,)). sqlite3 klebt den Wert dabei nicht in den Text, sondern gibt Befehl und Wert getrennt an SQLite weiter.

Der Befehl steht damit fest, bevor der Wert dazukommt. Was im Wert steht, bleibt ein Wert, auch ein Apostroph und auch ein ganzes Stück SQL. Mit O'Connor findet die Suche Liam. Mit x' OR '1'='1 findet sie niemanden, denn so heißt im Verein keiner.

Man könnte den Apostroph auch von Hand verdoppeln, so verlangt es SQL für Text: 'O''Connor'. Dann muss man aber an jeder Stelle daran denken, an der ein Wert in einen Befehl kommt. Und die eine Stelle, an die man nicht gedacht hat, ist dann die Lücke. In diesem Kurs kommt deshalb nie ein Wert per f-String in einen SQL-Befehl, auch nicht, wenn der Wert von dir selbst stammt.

Für eine Suche nach einem Teil des Namens gibt es LIKE (wie). Damit wird mit einem Muster verglichen, in dem % für beliebig viele Zeichen steht. nachname LIKE '%conn%' passt also auf O’Connor. Groß und klein ist LIKE dabei egal, allerdings nur bei den Buchstaben von A bis Z, bei Ä und ä nicht.

Mit einem Platzhalter gehören die Prozentzeichen in den Wert, nicht in den Befehl. Diesen Wert darf ein f-String bauen, denn dann baut er den Wert und nicht den Befehl. Steht dagegen '%?%' im Befehl, ist das Fragezeichen Text zwischen Anführungszeichen und kein Platzhalter mehr.

Ein Wert ist noch kein Tupel

Ein Wert ist noch kein Tupel
from verein import oeffnen

verbindung = oeffnen()


def suche(nachname):
    abfrage = "SELECT vorname, nachname, telefon FROM mitglieder WHERE nachname = ?"
    return verbindung.execute(abfrage, (nachname)).fetchall()


print(suche("Berger"))
verbindung.close()

Das ist der Fehler dieser Lektion, und er kommt ausgerechnet dann, wenn alles schon richtig aussieht. Ein einzelner Wert in Klammern ist noch kein Tupel. Die Klammern sind nur Klammern, wie in einer Rechnung. Zum Tupel wird er erst durch das Komma, (nachname,), wie in Lektion 5.7.

Ohne Komma bekommt execute den Text selbst, und Text ist für Python eine Folge von Zeichen. Das steht auch in der Meldung: Incorrect number of bindings supplied. The current statement uses 1, and there are 6 supplied. Der Befehl hat einen Platzhalter, geliefert wurden sechs Werte. Die sechs Buchstaben von Berger.

Was die Aufgabe will

Im Haushaltsbuch gibt es eine Suche nach dem Empfänger, gebaut mit einem f-String. Sie funktioniert, bis jemand nach der Pizzeria L’Angolo sucht. Dann ist ein Platzhalter fällig.

Zum Mitnehmen

Werte gehören über Platzhalter in einen SQL-Befehl und nie über einen f-String. Sonst reicht ein Apostroph, damit die Abfrage zerbricht, und ein Suchwort kann sie umschreiben (SQL-Injection). Ein einzelner Wert braucht im Tupel das Komma, also (name,), und bei LIKE gehören die Prozentzeichen in den Wert.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 5 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.