Sicherheitsrisiken
Datenbanken enthalten oft sensible Daten und ermöglichen die Durchführung gefährlicher Operationen. Die wichtigsten Aspekte für eine sichere Arbeit mit Nette Database sind:
- Verstehen des Unterschieds zwischen sicherer und unsicherer API
- Parametrisierte Abfragen verwenden
- Ordnungsgemäße Validierung der Eingabedaten
Was ist eine SQL-Injektion?
SQL-Injection ist das größte Sicherheitsrisiko bei der Arbeit mit Datenbanken. Sie tritt auf, wenn ungefilterte Benutzereingaben Teil einer SQL-Abfrage werden. Ein Angreifer kann seine eigenen SQL-Befehle einfügen und dadurch:
- Unbefugte Daten extrahieren
- Daten in der Datenbank ändern oder löschen
- die Authentifizierung zu umgehen
// ❌ GEFÄHRLICHER CODE - anfällig für SQL-Injection
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// Ein Angreifer könnte einen Wert eingeben wie: ' OR '1'='1
// Die resultierende Abfrage würde lauten: SELECT * FROM users WHERE name = '' OR '1'='1'
// Dies gibt alle Benutzer zurück
Das Gleiche gilt für den Database Explorer:
// ❌ GEFÄHRLICHER CODE - anfällig für SQL-Injection
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Sichere parametrisierte Abfragen
Der grundlegende Schutz gegen SQL-Injection sind parametrisierte Abfragen. Nette Database bietet mehrere Möglichkeiten, diese zu verwenden.
Der einfachste Weg ist die Verwendung von Fragezeichen-Platzhaltern:
// ✅ Sichere parametrisierte Abfrage
$database->query('SELECT * FROM users WHERE name = ?', $name);
// ✅ Sichere Bedingung im Explorer
$table->where('name = ?', $name);
Dies gilt für alle anderen Methoden im Database Explorer, die das Einfügen von Ausdrücken mit Fragezeichenplatzhaltern und Parametern erlauben.
Für die Klauseln INSERT
, UPDATE
oder WHERE
können Sie Werte in einem Array
übergeben:
// ✅ Sicheres EINFÜGEN
$database->query('INSERT INTO users', [
'name' => $name,
'email' => $email,
]);
// ✅ Sicheres EINFÜGEN im Explorer
$table->insert([
'name' => $name,
'email' => $email,
]);
Validierung von Parameterwerten
Parametrisierte Abfragen sind der Eckpfeiler einer sicheren Datenbankarbeit. Die an sie übergebenen Werte müssen jedoch mehrere Validierungsstufen durchlaufen:
Typprüfung
Die Sicherstellung des korrekten Datentyps von Parametern ist entscheidend – dies ist eine notwendige Bedingung für die sichere Verwendung von Nette Database. Die Datenbank geht davon aus, dass alle Eingabedaten den richtigen Datentyp haben, der der Spalte entspricht.
Wenn zum Beispiel $name
in den vorherigen Beispielen unerwartet ein Array statt einer Zeichenkette wäre, würde
Nette Database versuchen, alle seine Elemente in die SQL-Abfrage einzufügen, was zu einem Fehler führen würde. Verwenden Sie
daher niemals unvalidierte Daten aus $_GET
, $_POST
oder $_COOKIE
direkt in
Datenbankabfragen.
Format-Validierung
Auf der zweiten Ebene wird das Format der Daten überprüft. So wird beispielsweise sichergestellt, dass Zeichenketten UTF-8-kodiert sind und ihre Länge mit der Spaltendefinition übereinstimmt, oder es wird überprüft, ob numerische Werte in den zulässigen Bereich für den Datentyp der Spalte fallen.
Auf dieser Ebene können Sie sich teilweise auf die Datenbank selbst verlassen – viele Datenbanken weisen ungültige Daten zurück. Das Verhalten kann jedoch variieren: Einige schneiden lange Strings stillschweigend ab oder schneiden Zahlen ab, die außerhalb des zulässigen Bereichs liegen.
Bereichsspezifische Validierung
Die dritte Ebene umfasst logische Prüfungen, die für Ihre Anwendung spezifisch sind. So wird z. B. überprüft, ob Werte aus Auswahlfeldern mit den verfügbaren Optionen übereinstimmen, ob Zahlen in einen erwarteten Bereich fallen (z. B. Alter 0–150 Jahre) oder ob Beziehungen zwischen Werten sinnvoll sind.
Empfohlene Validierungsmethoden
- Verwenden Sie Nette Forms, die automatisch eine korrekte Validierung aller Eingaben vornehmen.
- Verwenden Sie Presenter und deklarieren Sie Parameter-Datentypen in
action*()
undrender*()
Methoden. - Oder implementieren Sie eine benutzerdefinierte Validierungsschicht mit Standard-PHP-Tools wie
filter_var()
.
Sicheres Arbeiten mit Columns
Im vorigen Abschnitt haben wir behandelt, wie man Parameterwerte richtig validiert. Bei der Verwendung von Arrays in SQL-Abfragen muss jedoch auch auf die Schlüssel geachtet werden.
// ❌ GEFÄHRLICHER CODE - Array-Schlüssel werden nicht sanitized
$database->query('INSERT INTO users', $_POST);
Bei INSERT- und UPDATE-Befehlen ist dies eine große Sicherheitslücke – ein Angreifer kann jede Spalte in der Datenbank
einfügen oder ändern. Er könnte z.B. is_admin = 1
einstellen oder beliebige Daten in sensible Spalten einfügen
(bekannt als Mass Assignment Vulnerability).
Bei WHERE-Bedingungen ist es sogar noch gefährlicher, da sie Operatoren enthalten können:
// ❌ GEFÄHRLICHER CODE - Array-Schlüssel werden nicht bereinigt
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// führt Abfrage aus WHERE (`Gehalt` > 100000)
Ein Angreifer kann diesen Ansatz nutzen, um systematisch die Gehälter von Mitarbeitern zu ermitteln. Er könnte mit einer Abfrage nach Gehältern über 100.000, dann unter 50.000 beginnen und durch schrittweises Eingrenzen des Bereichs die ungefähren Gehälter aller Mitarbeiter ermitteln. Diese Art von Angriff wird als SQL-Aufzählung bezeichnet.
Die Methoden where()
und whereOr()
sind noch flexibler und unterstützen SQL-Ausdrücke, einschließlich
Operatoren und Funktionen, sowohl in Schlüsseln als auch in Werten. Dies gibt einem Angreifer die Möglichkeit, komplexe
SQL-Injektionen durchzuführen:
// ❌ GEFÄHRLICHER CODE - Angreifer kann sein eigenes SQL einfügen
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// führt Abfrage aus WHERE (0) UNION SELECT name, salary FROM users WHERE (1)
Dieser Angriff beendet die ursprüngliche Bedingung mit 0)
, fügt seine eigene SELECT
mit
UNION
an, um sensible Daten aus der Tabelle users
zu erhalten, und schließt mit einer syntaktisch
korrekten Abfrage mit WHERE (1)
ab.
Spalte Whitelist
Für eine sichere Arbeit mit Spaltennamen benötigen Sie einen Mechanismus, der sicherstellt, dass Benutzer nur mit zugelassenen Spalten interagieren und keine eigenen hinzufügen können. Der Versuch, gefährliche Spaltennamen zu erkennen und zu blockieren (Blacklisting), ist unzuverlässig – ein Angreifer kann immer einen neuen Weg finden, einen gefährlichen Spaltennamen zu schreiben, den Sie nicht vorhergesehen haben.
Daher ist es viel sicherer, die Logik umzukehren und eine explizite Liste zulässiger Spalten zu definieren (Whitelisting):
// Spalten, die der Benutzer ändern darf
$allowedColumns = ['name', 'email', 'active'];
// Alle nicht autorisierten Spalten aus der Eingabe entfernen
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));
// ✅ Kann jetzt sicher in Abfragen verwendet werden, wie z. B.:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);
Dynamische Bezeichner
Für dynamische Tabellen- und Spaltennamen verwenden Sie den Platzhalter ?name
. Dadurch wird sichergestellt, dass
Bezeichner entsprechend der gegebenen Datenbanksyntax (z. B. unter Verwendung von Backticks in MySQL) korrekt escaped werden:
// ✅ Sichere Verwendung von vertrauenswürdigen Bezeichnern
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Ergebnis in MySQL: SELECT `Name` FROM `Benutzer`
Wichtig: Verwenden Sie das Symbol ?name
nur für vertrauenswürdige Werte, die im Anwendungscode definiert sind.
Für Werte, die vom Benutzer bereitgestellt werden, verwenden Sie wiederum eine Whitelist.
Andernfalls riskieren Sie Sicherheitslücken:
// ❌ DANGEROUS - niemals Benutzereingaben verwenden
$database->query('SELECT ?name FROM users', $_GET['column']);