Eine Shop-Suche zeigt einen Fehler
Ein kleiner Shop hat ein Suchfeld für seine Artikelliste. Ein Besucher tippt ein Wort mit Apostroph ein, zum Beispiel O'Brien.
Die Seite antwortet mit einer Zeile fremder Wörter aus der Maschine dahinter. Der Inhaber nennt das einen kleinen Fehler. Es ist ein Zeichen: Die Seite lässt Besucher einen Teil ihrer Fragen selbst schreiben.
Die eine Regel
Warnung
Dieses Lab ist ein Shop, der absichtlich unsicher ist. Greifen Sie nur Ihre eigene, private Kopie an und nichts sonst. Sie hat keine Verbindung ins Internet. Dieselben Versuche bei einem fremden System sind eine Straftat. Das gilt auch dann, wenn Sie nichts beschädigen wollen.
Am Ende dieser Lektion
Notiz
Ich kann eine Abfrage so bauen, dass eingetippter Text sie nicht verändern kann.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| Datenbank | ein geordneter Speicher für Datensätze, den ein Programm befragen kann | |
| Abfrage | eine Frage, die ein Programm an eine Datenbank stellt; auf Englisch Query | SELECT name FROM produkte |
| SQL-Injection | eingetippter Text, der die Abfrage eines Programms verändert | |
| Parameter | ein Wert, der neben der Abfrage gesendet wird und nie darin steht | ? |
| Zeichenkette | ein Stück Text in einem Programm; auf Englisch String | "Stift" |
| Eingabe | alles, was ein Besucher an eine Seite tippt oder sendet |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Eine zusammengesetzte Abfrage lässt sich ändern
Viele Programme bauen eine Abfrage, indem sie einen festen Anfang mit dem Text des Besuchers verbinden. Die Datenbank bekommt eine lange Zeichenkette. Sie kann nicht erkennen, welcher Teil vom Programmierer stammt und welcher vom Besucher.
Enthält der Text ein Anführungszeichen, beendet es den Text des Programmierers zu früh. Was danach kommt, liest die Datenbank als Teil der Abfrage.
Die Abfrage, die das Programm baut
# Ändern Sie begriff in "Notizbuch" und führen Sie den Block aus.
# Probieren Sie danach "Rock'n'Roll".
begriff = "Stift"
anfang = "SELECT name, preis FROM produkte WHERE name LIKE '%"
abfrage = anfang + begriff + "%'"
print(abfrage)
Beispiel: drei Suchen
| Der Besucher tippt | Die Datenbank erhält | Was passiert |
|---|---|---|
| Stift | ... LIKE '%Stift%' | Funktioniert wie gedacht |
| Rock'n'Roll | ... LIKE '%Rock'n'Roll%' | Das erste Apostroph beendet den Text zu früh, die Abfrage ist kaputt |
| x' AND 1=2 -- | ... LIKE '%x' AND 1=2 --%' | Der Besucher hat eine eigene Bedingung eingefügt |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Der Shop in Ihrem Lab
Ihr Lab enthält einen echten Shop mit dieser Schwäche. Starten Sie das Lab und führen Sie dann diese drei Befehle aus. Sie sehen nur hin. Der erste ist eine normale Suche. Der zweite sucht einen Namen mit Apostroph, den der Shop nicht verarbeiten kann. Der dritte zeigt die Codezeile des Shops, die die Abfrage baut.
Eine normale Suche
curl -s -G --data-urlencode "q=pen" http://app:8000/searchHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Ein Name mit Apostroph
curl -s -G --data-urlencode "q=Baker's" http://app:8000/searchHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Die Zeile, die die Abfrage baut
docker exec app grep -n "rows = db.execute" /tmp/app/app.pyHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Was die drei Ausgaben sagen
Die erste gibt Pen 1.2 aus, also Name und Preis. Die zweite gibt eine Zeile aus, die mit database error beginnt. Der Shop hat Ihr Apostroph direkt in seine Abfrage übernommen.
Die dritte gibt eine Zeilennummer und eine lange Codezeile aus. Suchen Sie die +-Zeichen darin. Jedes verbindet eingetippten Text mit der Abfrage.
Ein Parameter hält Text aus der Abfrage heraus
Ein Parameter behebt das. Das Programm sendet die Abfrage mit einem Platzhalter ? an der Stelle des Werts, als vorbereitete Anweisung (Prepared Statement). Den eingetippten Text sendet es getrennt.
Die Datenbank liest zuerst die Abfrage und legt ihre Form fest. Der Text kommt später und kann nur noch ein Wert sein. Kein Anführungszeichen darin kann etwas beenden.
Dieselbe Suche mit Parameter
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE produkte (name TEXT, preis REAL)")
db.execute("INSERT INTO produkte VALUES (?, ?)", ("Rock'n'Roll-Tasse", 8.5))
db.execute("INSERT INTO produkte VALUES (?, ?)", ("Stift", 1.2))
# Ändern Sie begriff und führen Sie den Block aus.
# Nichts, was Sie tippen, kann die Abfrage vorzeitig beenden.
begriff = "Rock'n'Roll"
zeilen = db.execute("SELECT name, preis FROM produkte WHERE name LIKE ?", ("%" + begriff + "%",)).fetchall()
print(zeilen)
Ein häufiger Fehler
Warnung
Viele glauben, es genüge, das Apostroph aus dem Text zu entfernen. Das stimmt nicht. Filter übersehen Fälle, und jeder übersehene Fall ist eine Lücke. Ein Parameter wirkt, weil er Text nie in die Abfrage mischt, egal wie raffiniert der Text ist. Nutzen Sie für jeden Wert einen Parameter.
Manches kann kein Parameter sein
Ein Parameter kann für einen Wert stehen. Er kann nicht für einen Spaltennamen oder eine Sortierung stehen. Lässt eine Seite Besucher nach Preis oder Name sortieren, muss das Wort trotzdem in die Abfrage.
Sicher ist eine kurze Liste erlaubter Wörter. Steht die Wahl des Besuchers nicht darauf, gilt ein Standardwert.
Nur Wörter aus der Liste
ERLAUBT = ["name", "preis"]
# Ändern Sie wahl auf "preis", dann auf "lager; weg", und führen Sie den Block aus.
wahl = "name"
if wahl in ERLAUBT:
spalte = wahl
else:
spalte = "name"
print("ORDER BY " + spalte)