Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Wie kann ich SQL-Injection in PHP verhindern?

Die richtige Unabhängig davon, welche Datenbank Sie verwenden, können Sie SQL-Injection-Angriffe vermeiden, indem Sie die Daten von SQL trennen , sodass Daten Daten bleiben und niemals interpretiert werden als Befehle vom SQL-Parser. Es ist möglich, SQL-Anweisungen mit korrekt formatierten Datenteilen zu erstellen, aber wenn Sie dies nicht vollständig tun Wenn Sie die Details verstehen, sollten Sie immer vorbereitete Anweisungen und parametrisierte Abfragen verwenden. Dies sind SQL-Anweisungen, die getrennt von Parametern an den Datenbankserver gesendet und analysiert werden. Auf diese Weise ist es einem Angreifer unmöglich, schädliches SQL einzuschleusen.

Dazu haben Sie grundsätzlich zwei Möglichkeiten:

  1. Mit 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. Mit 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 sich mit einer anderen Datenbank als MySQL verbinden, gibt es eine treiberspezifische zweite Option, auf die Sie sich beziehen können (z. B. pg_prepare()). und pg_execute() für PostgreSQL). PDO ist die universelle Option.

Korrekter Verbindungsaufbau

Beachten Sie dies bei der Verwendung von PDO um echt auf eine MySQL-Datenbank zuzugreifen vorbereitete Anweisungen werden standardmäßig nicht verwendet . Um dies zu beheben, müssen Sie die Emulation vorbereiteter Anweisungen deaktivieren. Ein Beispiel für das Erstellen einer Verbindung mit PDO ist:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', '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, ihn hinzuzufügen . Auf diese Weise stoppt das Skript nicht mit einem Fatal Error wenn etwas schief geht. Und es gibt dem Entwickler die Chance zu catch alle Fehler, die throw sind n als PDOException s.

Was ist obligatorisch , ist jedoch das erste setAttribute() Zeile, die PDO anweist, emulierte vorbereitete Anweisungen zu deaktivieren und real zu verwenden 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 (was einem möglichen Angreifer keine Chance gibt, bösartiges SQL einzuschleusen).

Obwohl Sie den charset festlegen können in den Optionen des Konstruktors ist zu beachten, dass „ältere“ Versionen von PHP (vor 5.3.6) ignorierte stillschweigend den charset-Parameter im DSN.

Erklärung

Die SQL-Anweisung, die Sie an prepare übergeben wird vom Datenbankserver analysiert und kompiliert. Durch Angabe von Parametern (entweder ein ? oder ein benannter Parameter wie :name im obigen Beispiel) teilen Sie der Datenbank-Engine mit, wonach Sie filtern möchten. Wenn Sie dann execute aufrufen , wird die vorbereitete Anweisung mit den von Ihnen angegebenen Parameterwerten kombiniert.

Wichtig dabei ist, dass die Parameterwerte mit der kompilierten Anweisung und nicht mit einem SQL-String kombiniert werden. Die SQL-Injection funktioniert, indem das Skript dazu gebracht wird, bösartige Zeichenfolgen einzuschließen, wenn es SQL zum Senden an die Datenbank erstellt. Indem Sie also das eigentliche SQL getrennt von den Parametern senden, begrenzen Sie das Risiko, am Ende mit etwas zu enden, das Sie nicht beabsichtigt haben.

Alle Parameter, die Sie senden, wenn Sie eine vorbereitete Anweisung verwenden, werden einfach als Zeichenfolgen behandelt (obwohl die Datenbank-Engine einige Optimierungen vornehmen kann, sodass Parameter natürlich auch als Zahlen enden können). Wenn im obigen Beispiel der $name Variable enthält 'Sarah'; DELETE FROM employees das Ergebnis wäre einfach eine Suche nach der Zeichenfolge "'Sarah'; DELETE FROM employees" , und Sie werden nicht mit einer leeren Tabelle enden .

Ein weiterer Vorteil der Verwendung von vorbereiteten Anweisungen besteht darin, dass, wenn Sie dieselbe Anweisung viele Male in derselben Sitzung ausführen, sie nur einmal analysiert und kompiliert wird, wodurch Sie einige Geschwindigkeitsgewinne erzielen.

Oh, und da Sie gefragt haben, wie man es für eine Einfügung macht, hier ist 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?

Während Sie weiterhin vorbereitete Anweisungen für die Abfrageparameter verwenden können, kann die Struktur der dynamischen Abfrage selbst nicht parametrisiert werden, und bestimmte Abfragefunktionen können nicht parametrisiert werden.

Für diese spezifischen Szenarien verwenden Sie am besten einen Whitelist-Filter, 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';
}