Frage Wie kann ich die SQL-Injektion in PHP verhindern?


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

$unsafe_variable = $_POST['user_input']; 

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

Das ist, weil der Benutzer etwas wie eingeben kann 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?


2781


Ursprung


Antworten:


Verwenden Sie vorbereitete Anweisungen und parametrisierte Abfragen. Dies sind SQL-Anweisungen, die vom Datenbankserver unabhängig von Parametern gesendet und analysiert werden. Auf diese Weise ist es einem Angreifer unmöglich, bösartiges SQL zu injizieren.

Sie haben grundsätzlich zwei Möglichkeiten, dies zu erreichen:

  1. Verwenden PDO (für jeden unterstützten Datenbanktreiber):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. Verwenden 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 verweisen können (z. pg_prepare() und pg_execute() für PostgreSQL). PDO ist die universelle Option.

Richtiges Einrichten der Verbindung

Beachten Sie, dass bei Verwendung PDO um auf eine MySQL-Datenbank zuzugreifen echt vorbereitete Aussagen sind nicht standardmäßig verwendet. Um dies zu beheben, müssen Sie die Emulation von vorbereiteten 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', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Im obigen Beispiel ist der Fehlermodus nicht unbedingt notwendig, aber es ist ratsam, es hinzuzufügen. Auf diese Weise stoppt das Skript nicht mit Fatal Error wenn etwas schief geht. Und es gibt dem Entwickler die Chance dazu catch irgendwelche Fehler, die sind thrown als PDOExceptions.

Was ist verpflichtendist jedoch der erste setAttribute() Zeile, die PDO anweist, emulierte vorbereitete Anweisungen zu deaktivieren und zu verwenden echt vorbereitete Aussagen. Dies stellt sicher, dass die Anweisung und die Werte nicht von PHP analysiert werden, bevor sie an den MySQL-Server gesendet werden (was einem möglichen Angreifer keine Chance gibt, schädliches SQL zu injizieren).

Obwohl Sie das einstellen können charset In den Optionen des Konstruktors ist es wichtig zu beachten, dass 'ältere' Versionen von PHP (<5.3.6) ignorierte den Parameter charset stillschweigend in der DSN.

Erläuterung

Was passiert, ist die SQL-Anweisung, an die Sie übergeben werden prepare wird vom Datenbankserver analysiert und kompiliert. Durch Angabe von Parametern (entweder a ? oder ein benannter Parameter wie :name Im obigen Beispiel teilen Sie der Datenbank-Engine mit, wo Sie filtern möchten. Dann, wenn du anrufst execute, die vorbereitete Anweisung wird mit den von Ihnen angegebenen Parameterwerten kombiniert.

Wichtig dabei ist, dass die Parameterwerte mit der kompilierten Anweisung und nicht mit einer SQL-Zeichenfolge kombiniert werden. Bei der SQL-Injizierung wird das Skript dazu veranlasst, schädliche Zeichenfolgen einzufügen, wenn SQL zum Senden an die Datenbank erstellt wird. Indem Sie das eigentliche SQL getrennt von den Parametern senden, begrenzen Sie das Risiko, dass Sie mit etwas enden, was Sie nicht beabsichtigt haben. Alle Parameter, die Sie bei der Verwendung einer vorbereiteten Anweisung senden, werden nur als Zeichenfolgen behandelt (obwohl die Datenbank-Engine möglicherweise eine Optimierung durchführt, so dass die Parameter natürlich auch als Zahlen enden können). Im obigen Beispiel, wenn $nameVariable enthält 'Sarah'; DELETE FROM employees Das Ergebnis wäre einfach eine Suche nach der Zeichenfolge "'Sarah'; DELETE FROM employees"und du wirst nicht enden eine leere Tabelle.

Ein weiterer Vorteil der Verwendung von vorbereiteten Anweisungen ist, dass wenn Sie dieselbe Anweisung mehrmals in der gleichen Sitzung ausführen, wird sie nur einmal analysiert und kompiliert, was Ihnen einige Geschwindigkeitsvorteile bringt.

Oh, und da du gefragt hast, 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(array('column' => $unsafeValue));

Können vorbereitete Anweisungen für dynamische Abfragen verwendet werden?

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

Für diese spezifischen 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';
}

7938



Warnung:   Der Beispielcode dieser Antwort (wie der Beispielcode der Frage) verwendet PHP mysql Erweiterung, die in PHP 5.5.0 veraltet und vollständig in PHP 7.0.0 entfernt wurde.

Wenn Sie eine aktuelle Version von PHP verwenden, wird die mysql_real_escape_string Die unten beschriebene Option ist nicht mehr verfügbar (obwohl mysqli::escape_string ist ein modernes Äquivalent). In diesen Tagen mysql_real_escape_string Option wäre nur für Legacy-Code auf einer alten Version von PHP sinnvoll.


Sie haben zwei Möglichkeiten - die Sonderzeichen in Ihrem unsafe_variableoder mithilfe einer parametrisierten Abfrage. Beide würden Sie vor der SQL-Injektion schützen. Die parametrisierte Abfrage wird als die bessere Methode angesehen, muss aber in PHP auf eine neuere mysql-Erweiterung umgestellt werden, bevor Sie sie verwenden können.

Wir werden die untere Schlagschnur abdecken, die zuerst entkommt.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Siehe auch die Details der mysql_real_escape_string Funktion.

Um die parametrisierte Abfrage zu verwenden, müssen Sie verwenden MySQLi eher als das MySQL Funktionen. Um Ihr Beispiel neu zu schreiben, würden wir etwas wie das folgende benötigen.

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

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Die Schlüsselfunktion, die Sie dort lesen möchten, wäre mysqli::prepare.

Wie andere bereits angedeutet haben, könnte es für Sie nützlich / einfacher sein, eine Ebene der Abstraktion mit etwas Ähnlichem zu verstärken PDO.

Bitte beachten Sie, dass der Fall, nach dem Sie gefragt haben, ziemlich einfach ist und komplexere Fälle komplexere Ansätze erfordern. Bestimmtes:

  • Wenn Sie die Struktur des SQL basierend auf Benutzereingaben ändern möchten, werden parametrisierte Abfragen nicht helfen, und das erforderliche Escaping wird nicht abgedeckt mysql_real_escape_string. In diesem Fall wäre es besser, die Eingaben des Benutzers über eine Whitelist weiterzuleiten, um sicherzustellen, dass nur "sichere" Werte zugelassen werden.
  • Wenn Sie Integer von Benutzereingaben in einer Bedingung verwenden, und nehmen Sie die mysql_real_escape_string nähern Sie sich dem Problem, das von Polynom in den Kommentaren unten. Dieser Fall ist komplizierter, da ganze Zahlen nicht in Anführungszeichen eingeschlossen sind. Sie können also damit umgehen, indem Sie überprüfen, dass die Benutzereingabe nur Ziffern enthält.
  • Es gibt wahrscheinlich andere Fälle, die mir nicht bekannt sind. Sie könnten finden Dies ist eine nützliche Ressource für einige der subtileren Probleme, denen Sie begegnen können.

1509



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

  • ein Faden
  • eine Zahl
  • eine Kennung
  • ein Syntaxschlüsselwort

und vorbereitete Aussagen deckt nur 2 von ihnen ab

Aber manchmal müssen wir unsere Abfrage noch dynamischer machen, indem wir Operatoren oder Identifikatoren hinzufügen.
Wir brauchen also verschiedene Schutztechniken.

Im Allgemeinen basiert solch ein Schutzansatz auf Whitelisting. In diesem Fall sollte jeder dynamische Parameter in Ihrem Skript fest codiert sein und aus diesem Satz ausgewählt werden.
Zum Beispiel, um dynamische Reihenfolge zu tun:

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

Es gibt jedoch noch eine andere Möglichkeit, Kennungen zu sichern - das Ausbrechen. Solange Sie einen Bezeichner in Anführungszeichen gesetzt haben, können Sie Backticks durch Verdoppelung entschlüsseln.

Als weiteren Schritt können wir uns eine wirklich geniale Idee ausdenken, einen Platzhalter (einen Proxy, um den tatsächlichen Wert in der Abfrage darzustellen) aus den vorbereiteten Anweisungen verwenden und einen Platzhalter eines anderen Typs erfinden - einen Platzhalter für einen Bezeichner.

Um die lange Geschichte kurz zu machen: Es ist ein Platzhalternicht vorbereitete Aussage kann als eine Silberkugel betrachtet werden.

Daher kann eine allgemeine Empfehlung wie folgt formuliert werden
Solange Sie der Abfrage dynamische Teile hinzufügen, die Platzhalter verwenden (und diese Platzhalter natürlich ordnungsgemäß verarbeitet werden), können Sie sicher sein, dass Ihre Abfrage sicher ist.

Dennoch gibt es ein Problem mit SQL-Syntax-Schlüsselwörtern (wie z AND, DESC und so), aber White-Listing scheint der einzige Ansatz in diesem Fall.

Aktualisieren

Obwohl es eine allgemeine Übereinstimmung hinsichtlich der besten Praktiken hinsichtlich des SQL-Injektionsschutzes gibt, gibt es immer noch viele schlechte Praktiken. Und einige von ihnen sind zu tief in den Köpfen von PHP-Benutzern verwurzelt. Zum Beispiel, auf dieser Seite gibt es (obwohl für die meisten Besucher unsichtbar) mehr als 80 gelöschte Antworten - Alle von der Community wegen schlechter Qualität entfernt oder schlechte und veraltete Praktiken zu fördern. Schlimmer noch, einige der schlechten Antworten sind nicht gelöscht, sondern eher prosperierend.

Beispielsweise, dort (1)  sind (2)  noch (3)  viele (4)  Antworten (5), einschließlich die zweithäufigste Antwort Ich schlage vor, dass Sie mit der manuellen Zeichenkette entkommen - ein veralteter Ansatz, der sich als unsicher erwiesen hat.

Oder es gibt eine etwas bessere Antwort, die nur darauf hindeutet eine andere Methode der String-Formatierung und rühmt sich sogar als ultimatives Allheilmittel. Natürlich nicht. Diese Methode ist nicht besser als die normale Formatierung von Strings, behält aber alle ihre Nachteile bei: Sie ist nur für Strings anwendbar und wie jede andere manuelle Formatierung ist sie im Wesentlichen eine optionale, nicht obligatorische Maßnahme, die für menschliche Fehler jeglicher Art anfällig ist.

Ich denke das alles wegen eines sehr alten Aberglaubens, unterstützt von solchen Behörden wie OWASP oder PHP-Handbuch, die Gleichheit zwischen dem "Entweichen" und dem Schutz vor SQL-Injektionen verkündet.

Unabhängig davon, was PHP-Handbuch seit Ewigkeiten gesagt hat, *_escape_string macht auf keinen Fall Daten sicher und war nie dazu gedacht. Abgesehen davon, dass sie für einen anderen SQL-Teil als String nutzlos ist, ist manuelles Escaping falsch, da es manuell statt automatisiert ist.

Und OWASP macht es noch schlimmer und betont die Flucht Benutzereingabe Das ist völliger Blödsinn: Solche Worte sollten im Zusammenhang mit dem Injektionsschutz nicht vorkommen. Jede Variable ist potenziell gefährlich - unabhängig von der Quelle! Oder mit anderen Worten - jede Variable muss korrekt formatiert sein, um in eine Abfrage eingefügt zu werden - unabhängig von der Quelle. Es ist das Ziel, das zählt. In dem Moment, in dem ein Entwickler beginnt, die Schafe von den Ziegen zu trennen (er denkt, ob eine bestimmte Variable "sicher" ist oder nicht), macht er seinen ersten Schritt in Richtung Katastrophe. Ganz zu schweigen davon, dass selbst die Formulierung darauf hindeutet, dass Massen am Einstiegspunkt entkommen, ähnlich wie die sehr magischen Zitate, die bereits verachtet, veraltet und entfernt wurden.

Also, anders als was auch immer "flieht", vorbereitete Aussagen ist die Maßnahme, die tatsächlich vor einer SQL-Injektion schützt (falls zutreffend).

Wenn Sie immer noch nicht überzeugt sind, hier ist eine Schritt-für-Schritt-Erklärung, die ich schrieb, Der Per Anhalter durch SQL Injection PreventionDort habe ich all diese Fragen im Detail erläutert und sogar einen Abschnitt zusammengestellt, der sich ausschließlich mit schlechten Praktiken und deren Offenlegung befasst.


940



Ich würde empfehlen, zu verwenden PDO (PHP Data Objects) um parametrisierte SQL-Abfragen auszuführen.

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

Und mit PDO anstelle von mysql_, mysqli_, und pgsql_ Funktionen, machen Sie Ihre App ein wenig mehr abstrahiert von der Datenbank, in dem seltenen Fall, dass Sie Datenbank-Provider wechseln müssen.


768



Benutzen PDO und vorbereitete Fragen.

($conn ist ein PDO Objekt)

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

565



Wie Sie sehen können, empfehlen die Leute, höchstens vorbereitete Aussagen zu verwenden. Es ist nicht falsch, aber wenn Ihre Abfrage ausgeführt wird nur einmal Pro Prozess würde es zu einer leichten Leistungseinbuße kommen.

Ich stand vor diesem Problem, aber ich glaube, ich habe es gelöst sehr ausgeklügelter Weg - die Art und Weise, wie Hacker Zitate vermeiden. Ich habe das in Verbindung mit emulierten vorgefertigten Anweisungen verwendet. Ich benutze es um zu verhindern alle Arten von möglichen SQL-Injection-Angriffen.

Mein Ansatz:

  • Wenn Sie erwarten, dass die Eingabe ganzzahlig ist, stellen Sie sicher, dass es ist Ja wirklich ganze Zahl. In einer variablen Sprache wie PHP ist es das sehr wichtig. Sie können zum Beispiel diese sehr einfache, aber leistungsstarke Lösung verwenden: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input); 

  • Wenn Sie etwas anderes von Integer erwarten hex es. Wenn Sie es verhexen, werden Sie alle Eingaben perfekt umgehen. In C / C ++ gibt es eine Funktion namens mysql_hex_string(), in PHP können Sie verwenden bin2hex().

    Mach dir keine Sorgen darüber, dass der entschlüsselte String eine 2x Größe seiner ursprünglichen Länge hat, auch wenn du ihn verwendest mysql_real_escape_stringPHP muss die gleiche Kapazität zuweisen ((2*input_length)+1)Das ist das Gleiche.

  • Diese Hex-Methode wird häufig verwendet, wenn Sie binäre Daten übertragen, aber ich sehe keinen Grund, warum Sie sie nicht für alle Daten verwenden, um SQL-Injection-Angriffe zu verhindern. Beachten Sie, dass Sie Daten mit voranstellen müssen 0x oder benutze die MySQL-Funktion UNHEX stattdessen.

So zum Beispiel die Abfrage:

SELECT password FROM users WHERE name = 'root'

Wird werden:

SELECT password FROM users WHERE name = 0x726f6f74

oder

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex ist die perfekte Flucht. Keine Möglichkeit zu injizieren.

Unterschied zwischen UNHEX-Funktion und 0x-Präfix

Es gab einige Diskussionen in Kommentaren, also möchte ich es endlich klarstellen. Diese beiden Ansätze sind sehr ähnlich, unterscheiden sich jedoch in einigen Punkten:

Das Präfix ** 0x ** kann nur für Datenspalten wie z Char, Varchar, Text, Block, Binär, etc.
Außerdem ist seine Verwendung etwas kompliziert, wenn Sie eine leere Zeichenfolge einfügen möchten. Sie müssen es vollständig durch ersetzen ''oder Sie erhalten einen Fehler.

UNHEX () arbeitet weiter irgendein Säule; Sie müssen sich nicht um die leere Zeichenfolge kümmern.


Hex-Methoden werden oft als Angriffe verwendet

Beachten Sie, dass diese Hex-Methode oft als SQL-Injection-Angriff verwendet wird, bei dem Integer genau wie Strings sind und nur mit Escapezeichen versehen werden mysql_real_escape_string. Dann können Sie die Verwendung von Anführungszeichen vermeiden.

Zum Beispiel, wenn Sie so etwas tun:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

Ein Angriff kann dich sehr einspritzen leicht. Betrachten Sie den folgenden injizierten Code, der von Ihrem Skript zurückgegeben wird:

SELECT ... WHERE ID = -1 union alle Wählen Sie tabellenname aus information_schema.tables

und jetzt einfach Tabellenstruktur extrahieren:

SELECT ... WHERE ID = -1 union alle wählen column_name aus information_schema.column wo table_name = 0x61727469636c65

Und dann wählen Sie einfach die gewünschten Daten aus. Ist es nicht cool?

Aber wenn der Kodierer einer injizierbaren Stelle es verhexen würde, wäre keine Injektion möglich, weil die Abfrage so aussehen würde: SELECT ... WHERE id = UNHEX('2d312075...3635')


498



WICHTIG

Der beste Weg, um SQL Injection zu verhindern, ist zu verwenden Vorbereitete Aussagen  anstatt zu entkommen, wie die akzeptierte Antwort demonstriert.

Es gibt Bibliotheken wie Aura.Sql und EasyDB Dadurch können Entwickler vorgefertigte Anweisungen einfacher verwenden. Um mehr darüber zu erfahren, warum vorbereitete Aussagen besser sind Stoppen der SQL-Injektion, beziehen auf Dies mysql_real_escape_string() Bypass und Kürzlich behobene Unicode SQL Injection-Schwachstellen in WordPress.

Injektionsprävention - mysql_real_escape_string ()

PHP hat eine spezielle Funktion, um diese Angriffe zu verhindern. Alles, was Sie tun müssen, ist den Mund einer Funktion zu benutzen, mysql_real_escape_string.

mysql_real_escape_string nimmt eine Zeichenfolge, die in einer MySQL-Abfrage verwendet wird, und gibt dieselbe Zeichenfolge zurück, bei der alle SQL-Injektionsversuche sicher maskiert wurden. Im Grunde wird es diese lästigen Zitate (') ersetzen, die ein Benutzer mit einem MySQL-sicheren Ersatz, einem gestrichenen Zitat, eingeben könnte.

HINWEIS: Sie müssen mit der Datenbank verbunden sein, um diese Funktion verwenden zu können!

// Verbindung zu MySQL herstellen

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Mehr Informationen finden Sie in MySQL - SQL Injection Prävention.


452