2769 Stimmen

Wie kann ich SQL-Injection in PHP verhindern?

Wenn Benutzereingaben ohne Änderung in eine SQL-Abfrage eingefügt werden, wird die Anwendung anfällig für SQL-Einschleusung , wie im folgenden Beispiel:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Das liegt daran, dass der Benutzer etwas eingeben kann wie value'); DROP TABLE table;-- und die Abfrage wird:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Was kann getan werden, um dies zu verhindern?

9682voto

Theo Punkte 128508

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:

  1. 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
    }
  2. 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';
}

60 Stimmen

Außerdem erlaubt die offizielle Dokumentation von mysql_query nur die Ausführung einer Abfrage, so dass jede andere Abfrage außer ; ignoriert wird. Auch wenn dies bereits veraltet ist, gibt es eine Menge Systeme unter PHP 5.5.0, die diese Funktion verwenden können. php.net/manual/de/function.mysql-query.php

21 Stimmen

Dies ist eine schlechte Angewohnheit, aber ist eine Post-Problem-Lösung: Nicht nur für SQL-Injektion, sondern für jede Art von Injektionen (zum Beispiel gab es ein View-Template-Injection-Loch in F3-Framework v2), wenn Sie eine fertige alte Website oder App ist von Injection-Defekte leiden, ist eine Lösung, um die Werte Ihrer supperglobalen vordefinierten vars wie $_POST mit escaped Werte bei Bootstrap neu zuweisen. Bei PDO ist es immer noch möglich zu escapen (auch für heutige Frameworks) : substr($pdo->quote($str, \PDO ::PARAM_STR), 1, -1)

28 Stimmen

Diese Antwort fehlt die Erklärung, was ist eine vorbereitete Anweisung - eine Sache - es ist ein Performance-Hit, wenn Sie eine Menge von vorbereiteten Anweisungen während Ihrer Anfrage verwenden und manchmal ist es Konten für 10x Performance Hit. Ein besserer Fall wäre die Verwendung von PDO mit deaktivierter Parameterbindung, aber deaktivierter Anweisungsvorbereitung.

1750voto

Matt Sheppard Punkte 113439

Um die parametrisierte Abfrage zu verwenden, müssen Sie entweder Mysqli oder PDO verwenden. Um Ihr Beispiel mit mysqli umzuschreiben, bräuchten wir etwas wie das Folgende.

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("server", "username", "password", "database_name");

$variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// "s" means the database expects a string
$stmt->bind_param("s", $variable);
$stmt->execute();

Die wichtigste Funktion, über die Sie sich informieren sollten, ist mysqli::prepare .

Wie bereits von anderen vorgeschlagen, kann es auch nützlich/einfacher sein, eine Abstraktionsebene höher zu gehen mit etwas wie PDO .

Bitte beachten Sie, dass der Fall, nach dem Sie gefragt haben, recht einfach ist und dass komplexere Fälle möglicherweise komplexere Ansätze erfordern. Im Besonderen:

  • Wenn Sie die Struktur von SQL auf der Grundlage von Benutzereingaben ändern möchten, sind parametrisierte Abfragen nicht hilfreich, und das erforderliche Escaping wird nicht von mysql_real_escape_string . In solchen Fällen ist es besser, die Benutzereingabe durch eine Whitelist zu leiten, um sicherzustellen, dass nur "sichere" Werte durchgelassen werden.

2 Stimmen

Mit mysql_real_escape_string ist genug oder muss ich auch parametrisiert verwenden?

11 Stimmen

@peimanF. halten eine gute Praxis der Verwendung parametrisierte Abfragen, auch auf einem lokalen Projekt. Mit parametrisierten Abfragen sind Sie garantiert dass es keine SQL-Injektion geben wird. Denken Sie aber daran, dass Sie die Daten bereinigen sollten, um einen falschen Abruf zu vermeiden (d.h. XSS-Injektion, wie z.B. das Einfügen von HTML-Code in einen Text) mit htmlentities zum Beispiel

3 Stimmen

@peimanF. Gute Praxis, um parametrisierte Abfragen und binden Werte, aber echte Escape-String ist gut für jetzt

1184voto

Your Common Sense Punkte 154708

Jede Antwort hier deckt nur einen Teil des Problems ab. In der Tat gibt es vier verschiedene Abfrageteile, die wir dynamisch zu SQL hinzufügen können: -

  • eine Zeichenfolge
  • eine Zahl
  • eine Kennung
  • ein Syntax-Schlüsselwort

Und die vorbereiteten Erklärungen decken nur zwei davon ab.

Aber manchmal müssen wir unsere Abfrage noch dynamischer gestalten und auch Operatoren oder Bezeichner hinzufügen. Dann brauchen wir andere Schutztechniken.

Im Allgemeinen basiert ein solcher Schutzansatz auf Whitelisting .

In diesem Fall sollte jeder dynamische Parameter in Ihrem Skript fest kodiert und aus dieser Menge ausgewählt werden. Zum Beispiel, um eine dynamische Bestellung durchzuführen:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Um den Prozess zu erleichtern, habe ich ein Hilfsfunktion für die Whitelist die die gesamte Arbeit in einer Zeile erledigt:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Es gibt noch eine andere Möglichkeit, Bezeichner zu schützen - das Escape-Verfahren, aber ich halte mich lieber an das Whitelisting, da dies ein robusterer und eindeutigerer Ansatz ist. Solange Sie einen Bezeichner in Anführungszeichen setzen, können Sie das Anführungszeichen maskieren, um ihn sicher zu machen. Zum Beispiel müssen Sie standardmäßig für mysql das Anführungszeichen verdoppeln, um es zu umgehen . Für andere DBMS gelten andere Escaping-Regeln.

Es gibt jedoch ein Problem mit SQL-Syntax-Schlüsselwörtern (wie AND , DESC und so weiter), aber in diesem Fall scheint eine weiße Liste die einzige Lösung zu sein.

Eine allgemeine Empfehlung kann also wie folgt formuliert werden

  • Jede Variable, die ein SQL-Datenliteral darstellt (oder, um es einfach auszudrücken, eine SQL-Zeichenfolge oder eine Zahl), muss durch eine vorbereitete Anweisung hinzugefügt werden. Keine Ausnahmen.
  • Alle anderen Abfrageteile, wie z. B. ein SQL-Schlüsselwort, ein Tabellen- oder Feldname oder ein Operator, müssen über eine Whitelist gefiltert werden.

Update

Obwohl es eine allgemeine Vereinbarung über die besten Praktiken zum Schutz vor SQL-Injection gibt, gibt es aber auch viele schlechte Praktiken. Und einige davon sind zu tief in den Köpfen der PHP-Nutzer verwurzelt. Zum Beispiel gibt es auf dieser Seite (obwohl für die meisten Besucher unsichtbar) mehr als 80 gelöschte Antworten - alle von der Gemeinschaft wegen schlechter Qualität oder Förderung schlechter und veralteter Praktiken entfernt. Schlimmer noch, einige der schlechten Antworten werden nicht gelöscht, sondern gedeihen sogar.

Zum Beispiel, dort(1) sind(2) still(3) viele(4) Antworten(5) , einschließlich der die zweitmeisten Stimmen für die Antwort Sie schlagen vor, die Zeichenketten manuell zu entschlüsseln - ein veralteter Ansatz, der sich als unsicher erwiesen hat.

Oder es gibt eine etwas bessere Antwort, die vorschlägt, einfach eine andere Methode der String-Formatierung und rühmt es sogar als das ultimative Allheilmittel. Das ist sie natürlich nicht. Diese Methode ist nicht besser als die reguläre Formatierung von Zeichenketten, hat aber alle ihre Nachteile: Sie ist nur auf Zeichenketten anwendbar und wie jede andere manuelle Formatierung ist sie im Wesentlichen eine optionale, nicht obligatorische Maßnahme, die anfällig für menschliche Fehler jeglicher Art ist.

Ich denke, dass all dies auf einen sehr alten Aberglauben zurückzuführen ist, der von solchen Autoritäten wie OWASP o das PHP-Handbuch die die Gleichheit zwischen dem "Escaping" und dem Schutz vor SQL-Injections proklamiert.

Ungeachtet dessen, was im PHP-Handbuch seit langem steht, *`_escape_string` macht Daten keineswegs sicher** und war auch nie dazu bestimmt. Abgesehen davon, dass es für alle SQL-Teile außer Strings nutzlos ist, ist das manuelle Escaping falsch, weil es manuell und nicht automatisiert ist.

Und OWASP macht es noch schlimmer, indem es den Schwerpunkt auf das Entkommen legt Benutzereingabe was völliger Unsinn ist: Im Zusammenhang mit dem Injektionsschutz sollte es solche Wörter nicht geben. Jede Variable ist potenziell gefährlich - unabhängig von ihrer Quelle! Oder anders ausgedrückt: Jede Variable muss richtig formatiert sein, um in eine Abfrage eingefügt werden zu können - unabhängig von der Quelle. Es ist das Ziel, auf das es ankommt. In dem Moment, in dem ein Entwickler beginnt, die Schafe von den Ziegen zu trennen (indem er darüber nachdenkt, ob eine bestimmte Variable "sicher" ist oder nicht), macht er/sie den ersten Schritt in Richtung Katastrophe. Ganz zu schweigen davon, dass sogar der Wortlaut ein Bulk-Escaping am Eingabepunkt nahelegt, das an die magische Anführungszeichen-Funktion erinnert, die bereits verachtet, veraltet und entfernt wurde.

Anders als bei der "Flucht" sind vorbereitete Erklärungen also ist die Maßnahme, die tatsächlich vor SQL-Injection schützt (falls zutreffend).

903voto

Kibbee Punkte 64039

Ich empfehle die Verwendung von PDO (PHP Data Objects), um parametrisierte SQL-Abfragen durchzuführen.

Dies schützt nicht nur vor SQL-Injection, sondern beschleunigt auch die Abfragen.

Und durch die Verwendung von PDO anstelle von mysql_ , mysqli_ y pgsql_ Funktionen machen Sie Ihre Anwendung ein wenig abstrakter von der Datenbank, für den seltenen Fall, dass Sie den Datenbankanbieter wechseln müssen.

15 Stimmen

Diese Antwort ist irreführend. PDO ist kein Zauberstab, der Ihre Abfragen allein durch seine Anwesenheit schützt. Sie sollten jede Variable in Ihrer Abfrage durch einen Platzhalter ersetzen um einen Schutz vor PDO zu erhalten.

0 Stimmen

Haben Sie irgendwelche Quellen oder können Sie näher erläutern, was Sie meinen, wenn Sie sagen substitute every variable in your query with a placeholder Meinen Sie die Sache mit dem Bindungswert?

0 Stimmen

@Daniel L. VanDenBosch könnten wir diese Host-Variablen nennen? Die meisten eingebetteten SQL-Systeme nennen sie so. Wenn sie keine Platzhalter sind, sind sie Konstanten, auch wenn der Wert in ein Host-Feld gelangt, das andere Werte enthalten kann. Die Minimierung der Anzahl von Variablen sorgt für einen vorhersehbaren Zugriffspfad, verringert aber natürlich die Wiederverwendbarkeit.

669voto

Imran Punkte 81131

Verwenden Sie PDO und vorbereitete Abfragen.

( $conn es un PDO Objekt)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X