El richtig um SQL-Injection-Angriffe zu vermeiden, unabhängig davon, welche Datenbank Sie verwenden, ist die Daten von SQL trennen damit Daten Daten bleiben und niemals interpretiert werden als Befehle durch den SQL-Parser. Es ist möglich, eine SQL-Anweisung mit korrekt formatierten Datenteilen zu erstellen, aber wenn Sie das nicht tun vollständig die Details zu verstehen, sollten Sie immer vorbereitete Anweisungen und parametrisierte Abfragen verwenden. Dies sind SQL-Anweisungen, die an den Datenbankserver gesendet und von diesem unabhängig von Parametern analysiert werden. Auf diese Weise ist es für einen Angreifer unmöglich, bösartiges SQL einzuschleusen.
Sie haben grundsätzlich zwei Möglichkeiten, dies zu erreichen:
-
Verwendung von PDO (für jeden unterstützten Datenbanktreiber):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute([ 'name' => $name ]);
foreach ($stmt as $row) {
// Do something with $row
}
-
Verwendung von MySQLi (für MySQL):
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
// Do something with $row
}
Wenn Sie eine Verbindung zu einer anderen Datenbank als MySQL herstellen, gibt es eine treiberspezifische zweite Option, auf die Sie sich beziehen können (zum Beispiel, pg_prepare()
y pg_execute()
für PostgreSQL). PDO ist die universelle Option.
Richtiges Einrichten der Verbindung
PDO
Beachten Sie, dass bei der Verwendung von PDO um auf eine MySQL-Datenbank zuzugreifen real vorbereitete Erklärungen sind standardmäßig nicht verwendet . Um dies zu beheben, müssen Sie die Emulation von vorbereiteten Anweisungen deaktivieren. Ein Beispiel für die Erstellung einer Verbindung mit PDO ist:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Im obigen Beispiel ist der Fehlermodus nicht unbedingt erforderlich, aber es wird empfohlen, sie hinzuzufügen . Auf diese Weise wird PDO Sie über alle MySQL-Fehler informieren, indem es die PDOException
.
Was ist obligatorisch ist jedoch die erste setAttribute()
Zeile, die PDO anweist, emulierte vorbereitete Anweisungen zu deaktivieren und die real vorbereitete Erklärungen. Dadurch wird sichergestellt, dass die Anweisung und die Werte nicht von PHP geparst werden, bevor sie an den MySQL-Server gesendet werden (wodurch ein möglicher Angreifer keine Chance hat, bösartiges SQL zu injizieren).
Sie können zwar die charset
in den Optionen des Konstruktors ist es wichtig zu beachten, dass 'ältere' Versionen von PHP (vor 5.3.6) den Zeichensatz-Parameter stillschweigend ignoriert im DSN.
Mysqli
Für mysqli müssen wir die gleiche Routine befolgen:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
$dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
$dbConnection->set_charset('utf8mb4'); // charset
Erläuterung
Die SQL-Anweisung, die Sie an prepare
wird vom Datenbankserver geparst und kompiliert. Durch die Angabe von Parametern (entweder ein ?
oder ein benannter Parameter wie :name
im obigen Beispiel) teilen Sie der Datenbankmaschine mit, wonach Sie filtern möchten. Wenn Sie dann execute
wird die vorbereitete Anweisung mit den von Ihnen angegebenen Parameterwerten kombiniert.
Wichtig ist dabei, dass die Parameterwerte mit der kompilierten Anweisung kombiniert werden und nicht mit einem SQL-String. SQL-Injection funktioniert, indem das Skript dazu gebracht wird, bösartige Zeichenfolgen einzuschließen, wenn es SQL erstellt, das an die Datenbank gesendet wird. Wenn Sie also die eigentliche SQL-Anweisung getrennt von den Parametern senden, verringern Sie das Risiko, etwas zu erhalten, was Sie nicht beabsichtigt haben.
Alle Parameter, die Sie bei der Verwendung einer vorbereiteten Anweisung senden, werden einfach als Zeichenketten behandelt (obwohl die Datenbank-Engine einige Optimierungen vornehmen kann, so dass die Parameter natürlich auch als Zahlen ausgegeben werden können). Im obigen Beispiel, wenn die $name
Variable enthält 'Sarah'; DELETE FROM employees
wäre das Ergebnis einfach eine Suche nach der Zeichenfolge "'Sarah'; DELETE FROM employees"
und Sie werden am Ende nicht mit ein leerer Tisch .
Ein weiterer Vorteil der Verwendung von vorbereiteten Anweisungen besteht darin, dass dieselbe Anweisung, wenn sie mehrmals in derselben Sitzung ausgeführt wird, nur einmal geparst und kompiliert wird, was einen Geschwindigkeitsgewinn bedeutet.
Oh, und da Sie gefragt haben, wie man das für eine Einfügung macht, hier ein Beispiel (mit PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute([ 'column' => $unsafeValue ]);
Können vorbereitete Anweisungen für dynamische Abfragen verwendet werden?
Sie können zwar weiterhin vorbereitete Anweisungen für die Abfrageparameter verwenden, aber die Struktur der dynamischen Abfrage selbst kann nicht parametrisiert werden, und bestimmte Abfragefunktionen können nicht parametrisiert werden.
Für diese speziellen Szenarien ist es am besten, einen Whitelist-Filter zu verwenden, der die möglichen Werte einschränkt.
// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}