Prepared Statements und SQL Injections

MerlinaMendel

Grünschnabel
Für eine Präsentation über eine einfache Website mit Datenbank im informatikunterricht Klasse 10 werde ich stark auf Prepared Statements eingehen, da ich zuvor den Abfragealgorithmus (mit Prepared Statements) erkläre. Aber wieso ist man durch die Verwendung von ihnen vor SQL-Injections geschützt, und was genau passiert im Fall von SQL-Injection?
 
Hi,

vereinfacht gesagt sorgen die PreparedStatements dafür, dass die Trennung zwischen SQL-Befehlen und Daten sauber vorhanden ist.
Eine SQL-Inejction will ja aus dem Datenmodus ("WHERE xy = 'foobar'", hier quasi das "foobar") ausbrechen und die SQL-Engine dazu zu bringen, andere Bedingungen oder Befehle auszuführen.

Mit Prepared-Statements bekommt die Datenbank vorab schon das SQL, welches ausgeführt werden soll. Es kann somit schon vorab genau sagen, was zu den Befehlen / Bedingungen gehört (SQL-Syntax) und wo die Daten liegen.

Hoffe ich habe das verständlich rübergebracht.

Grüsse,
BK
 
Wie @Bratkartoffel richtig sagt, zentral für SQL-Injections und eine Reihe von anderen (z. B. Cross-Site-Scripting ["XSS"]) ist die fehlende Trennung von Befehl und Daten. Bei XSS gibst du in HTML-Code deine Daten einfach so aus, ggf. werden dann die Daten auch als HTML-Code interpretiert - da ist der Fehler!

Selbst mit Prepared Statements hat man Probleme bei LIKE Anfragen. Zum Beispiel möchte man alle Nutzer haben, deren Namen einen vom Nutzer gewählten Teilstring beinhalten:
SQL:
SELECT name FROM users WHERE name LIKE '%ComFreek%'
Das sucht alle Nutzer, die ComFreek im Namen haben.

In dieser StackOverflow-Antwort wird vorgeschlagen, den Befehl (die zwei Wildcards %) und die Daten ($_POST['user']) zu mischen:
PHP:
// das klebt %, den vom Nutzer gewählten Teilstring und noch ein % zusammen
$param = '%' . $userChosenSubstring . '%';

// Wir spezifizieren den Befehl
$stmt = $db->prepare("SELECT id,Username FROM users WHERE Username LIKE ?");

// Und geben die Daten explizit an
$stmt->bind_param('s', $param);

// Leider haben wir trotzdem Befehl & Daten im LIKE vermischt
$stmt->execute();

Wenn ich nun als Nutzer "Com%Freek" eingebe, so wird die Anwendung mir z. B. auch "ComBratkartoffelFreek" ausgeben können. Erneut liegt der Fehler darin, dass keine Schnittstelle existiert, um Befehl und Daten separieren zu können. Übrigens erhöht sich auch die Laufzeit für das SQL-Query, je mehr Wildcards (%) enthalten sind. Dementsprechend könnte ein Angreifer das System in die Knie zwingen.
Ein vollkommen analoges Problem existiert bei regulären Ausdrücken.

Wenn du magst, könntest du die LIKE-Problematik ja als Ausblick in deine Präsentation einbauen ;) Falls du noch Fragen hast oder ich mich unklar ausgedrückt habe, scheu nicht nochmal rückzufragen!
 
Zuletzt bearbeitet:
Kleine Ergänzung zu Prepared Statements: Es gibt zu dem -sehr wichtigen- Sicherheitsaspekt auch eine Performanceaspekt:
Die meisten Datenbanksysteme verwenden einen Abfragespeicher, in dem die letzten Abfragen behalten werden (ggf. inkl. Ablaufplänen etc). Jede neue Abfrage wird erstmal geprüft, ob sie schon in dem Speicher vorhanden ist und ob man somit einfach das Ergebnis ausgeben kann oder auf vorhandene Daten im Cache zurückgreifen kann.
Arbeitet man ohne Prepared Statements (Ad hoc Abfragen), so sammelt sich dort einiges an Daten. Bei Prepared Statements liegt die Abfrage nur einmal im Speicher und wird zur Laufzeit um die Varibalen ergänzt --> Weniger Datenmüll, höhere Performance
 
Zuletzt bearbeitet:
Also bei SQL-Injections ersetzt der Hacker die Abfrage durch selbstgeschriebenen SQL-Code (wie DELETE *), und durch Prepared Statements hat der Hacker nur Zugang zu den Benutzereingaben, aber nicht zur Abfrage bzw. zur Datenbank. Richtig?
 
Der Angreifer ersetzt den Code in der Regel nicht, er ergänzt ihn. An die normale Abfrage eines Usernamens könnte er z.B. ein DELETE, DROP TABLE o.ä, anfügen. In der Regeln schließen sich aber ehr weitere Abfragen ab, denn Daten zu haben ist wertvoller als Daten zu löschen :)
Die Prepared Statements sorgen dafür, dass die Abfrage nur so gegen die Datenbank laufen kann, wie der Programmierer es vorgesehen hat. Es sollte daher nicht möglich sein, eigenen Code einzuschleusen.
 
Hi

also es geht darum, dass bei direkter Verwendung von externen Werten (Benutzereingaben usw.) der Benutzer etc. hergehen kann und die Werte gezielt so wählen, dass die DB durcheinanderkommt, was jetzt ein Wert und was der Befehl selbst ist. Bei Prep.Statements gibt man den Befehl und die Werte komplett getrennt zur DB, statt sie zuerst zusammenzusetzen, daher kann die DB auch nicht durcheiannderkommen. Das wurde ja schon ausreichend gesagt.

Drei Punkte dazu aber noch:

a) "Falsche" Werte müssen gar nicht die Absicht haben, etwas blödes zu machen. Angenommen man hat in purem SQL folgendes Insert:
Code:
#Für Michael Jackson
INSERT INTO personen(name) VALUES("Michael Jackson");
Die Anführungszeichen hier markieren Beginn und Ende des Werts, der Rest ist SQL.
Sowas kann man natürlich nicht machen, da sich die DB dann beschweren wird.
Code:
#Für Michael Jackson
INSERT INTO personen(name) VALUES("Michael "Jacks"on"""""""""""");
Und wenn man einen leicht anderen Wert einfügen will:
Code:
#Für Michael Jackson, der "King of Pop"
INSERT INTO personen(name) VALUES("Michael Jackson, der "King of Pop"");
Und schon hat man eine falsche Anweisung - vor King ist der Wert für die DB schon wieder aus, Kinf of Pop ist kein Sql, und danach die zwei "" helfen auch nicht mehr.
Um das korrekt zu machen müsste man sowas tun:
Code:
#Für Michael Jackson, der "King of Pop"
INSERT INTO personen(name) VALUES("Michael Jackson, der \"King of Pop\"");
Also ein \ vor den " die nicht als Stringgrenzen gedacht sind, dann gehts (die \ sind nach dem Einfügen nicht mehr im Text, die sind nur Markierer für die DB).

...und wenn man das jetzt aus PHP aufruft, mit einem namen den der Benutzer eingegeben hat
Code:
query('INSERT INTO personen(name) VALUES("$name");');
muss man eben entweder dafür sorgen, dass zB. alle " in $name zuerst \" werden usw. (es gibt einige problematische Zeichen, die man so maskieren muss), oder Prepared Statements verwenden.

Wenn nicht, dann kann es zu solchen unabsichtlichen Problemen kommen, UND Benutzer können mit gezielten Werten natürlich auch gezielt die SQL-Anweisung verändern.

b) Welche Werten (von wo) kann man nicht vertrauen?
Idealerweise "allen", die nicht im (PHP-)Programm selbst erzeugt werden. Dinge die der Benutzer zB. in ein Formular eingibt ist relativ klar. Nicht so klar sind manchen Leuten, dass auch zB. folgende Sachen problematisch sein können: Werte, die man aus der DB abgefragt hat (die zuerst also schon dort waren), wenn sie ursprünglich Benutzereingaben etc. waren. Cookies. Eine Liste von Dateinamen in einem ordner am eigenen Server, die man sich automatisch zusammenstellt, wenn es Uploads von Benutzern sind. usw.usw.

c) Was kann man mit Sql-Injections schlimmstenfalls anrichten?
Hängt zwar sehr von der originalen Abfrage, DB-Einstellungen, usw. ab, und auch was das PHP-Programm mit Abfrageergebnissen machen, aber:
Alles in der DB nach Belieben ändern, ergänzen, löschen; PHP-Code mit falschen Abfrageergebnissen beliebig verwirren, und wenn der Benutzer bestimmte Abfrageergebnisse irgendwo auch sehen kann dann den gesamten DB-Inhalt anschauen.
 
Ich glaube, jetzt habe ich es verstanden. :) - hat gleich 2 Vorteile:
1. es erspart mir eine peinliche Situation, wenn die Frage "wieso machst du den ganzen aufwand mit diesen Prepared Statements?" kommt
und
2. lernen die anderen Schüler hoffentlich, was dumm ist (ohne Prepared Statements), wieso es dumm ist (SQL-injection bzw. das andere was sheel gesagt hat) und wie man es richtig macht (Prepared Statements) ;)
 
Zuletzt bearbeitet:
Zurück