mysql gegenmaßnahmen - Wie kann ich die SQL-Injection in PHP verhindern?



1=1 beispiel (24)

Wenn Benutzereingaben ohne Änderung in eine SQL-Abfrage eingefügt werden, wird die Anwendung für SQL-Injection anfällig, 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 wie value'); DROP TABLE table;-- eingeben kann value'); DROP TABLE table;-- value'); DROP TABLE table;-- , und die Abfrage wird zu:

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

Was kann getan werden, um dies zu verhindern?


Answers

Wenn möglich, geben Sie die Typen Ihrer Parameter an. Es funktioniert jedoch nur mit einfachen Typen wie int, bool und float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Sicherheitswarnung : Diese Antwort entspricht nicht den bewährten Sicherheitsmethoden. Die Flucht ist nicht ausreichend, um die SQL-Injection zu verhindern , verwenden Sie stattdessen vorbereitete Anweisungen . Verwenden Sie die unten beschriebene Strategie auf eigenes Risiko. (Außerdem wurde mysql_real_escape_string() in PHP 7 entfernt.)

Sie könnten so etwas grundlegendes tun:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Dies löst nicht jedes Problem, aber es ist ein sehr guter Sprungbrett. Ich habe offensichtliche Elemente wie das Überprüfen der Existenz der Variablen und des Formats (Zahlen, Buchstaben usw.) ausgelassen.


Ein einfacher Weg wäre die Verwendung eines PHP-Frameworks wie CodeIgniter oder Laravel das über integrierte Funktionen wie Filterung und Active-Record verfügt, sodass Sie sich nicht um diese Nuancen kümmern müssen.


Was auch immer Sie am Ende verwenden, stellen Sie sicher, dass Sie Ihre Eingaben nicht bereits von magic_quotes oder einem anderen gut gemeinten Müll magic_quotes , und führen Sie sie stripslashes durch stripslashes oder was auch immer, um sie zu stripslashes .


Die Verwendung von PDO und MYSQLi ist eine bewährte MYSQLi , um SQL-Injektionen zu verhindern. Wenn Sie jedoch wirklich mit MySQL-Funktionen und MYSQLi arbeiten möchten, sollten Sie dies besser MYSQLi

mysql_real_escape_string()

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Es gibt weitere Möglichkeiten, dies zu verhindern: Identifizieren - Wenn die Eingabe eine Zeichenfolge, eine Zahl, ein Zeichen oder ein Array ist, gibt es so viele eingebaute Funktionen, um dies zu erkennen. Es ist auch besser, diese Funktionen zur Überprüfung der Eingangsdaten zu verwenden.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

Und es ist viel besser, diese Funktionen zur Überprüfung von Eingabedaten zu verwenden mysql_real_escape_string.


Meines Erachtens ist es am besten, die SQL-Injection in Ihrer PHP-Anwendung (oder auch einer Webanwendung) generell zu verhindern, indem Sie über die Architektur Ihrer Anwendung nachdenken. Wenn Sie sich nur vor einer SQL-Injection schützen können, ist die Verwendung einer speziellen Methode oder Funktion, die The Right Thing jedes Mal ausführt, wenn Sie mit der Datenbank sprechen, dass Sie dies falsch machen. Auf diese Weise ist es nur eine Frage der Zeit, bis Sie vergessen, Ihre Abfrage irgendwann in Ihrem Code richtig zu formatieren.

Die Übernahme des MVC-Musters und eines Frameworks wie CakePHP oder CodeIgniter ist wahrscheinlich der richtige Weg: Häufige Aufgaben wie das Erstellen sicherer Datenbankabfragen wurden gelöst und zentral in solchen Frameworks implementiert. Sie helfen Ihnen dabei, Ihre Webanwendung auf sinnvolle Art und Weise zu organisieren und machen Ihnen mehr Gedanken zum Laden und Speichern von Objekten als zum sicheren Erstellen einzelner SQL-Abfragen.



Die parametrisierte Abfrage- und Eingabevalidierung ist der Weg zu gehen. Es gibt viele Szenarien, unter denen die SQL-Injection auftreten kann, obwohl mysql_real_escape_string() verwendet wurde.

Diese Beispiele sind anfällig für SQL-Injection:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

oder

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

In beiden Fällen können Sie ' nicht verwenden ' um die Kapselung zu schützen.

Source : Die unerwartete SQL-Injektion (wenn die Flucht nicht ausreichend ist)


Ich denke, wenn jemand PHP und MySQL oder einen anderen DataBase-Server verwenden möchte:

  1. Überlegen Sie sich, PDO (PHP Data Objects) zu lernen - es handelt sich um eine Datenbankzugriffsschicht, die eine einheitliche Zugriffsmethode auf mehrere Datenbanken bietet.
  2. Denken Sie darüber nach, MySQLi lernenMySQLi
  3. Verwenden Sie native PHP-Funktionen wie: strip_tags , mysql_real_escape_string() oder wenn die Variable numerisch ist (int)$foo. Lesen Sie mehr über Art von Variablen in PHP here . Wenn Sie Bibliotheken wie PDO oder MySQLi verwenden, verwenden Sie immer PDO::quote() und mysqli_real_escape_string() .

Bibliotheksbeispiele:

---- gU

----- Keine Platzhalter - reif für SQL-Injection! Es ist schlecht

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- Unbenannte Platzhalter

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Benannte Platzhalter

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

Die PDO gewinnt diesen Kampf mit Leichtigkeit. Durch die Unterstützung von zwölf verschiedenen Datenbanktreibern und benannten Parametern können wir den geringen Leistungsverlust ignorieren und uns an seine API gewöhnen. Unter Sicherheitsaspekten sind beide sicher, solange der Entwickler sie so verwendet, wie sie verwendet werden sollen

Während sowohl PDO als auch MySQLi recht schnell sind, ist MySQLi bei Benchmarks unwichtig schneller - ~ 2,5% für nicht vorbereitete Anweisungen und ~ 6,5% für vorbereitete.

Testen Sie bitte jede Abfrage in Ihrer Datenbank. Dies ist eine bessere Methode, um die Injektion zu verhindern.


Für diejenigen, die nicht sicher sind, wie man PDO verwendet (von den mysql_Funktionen kommt), habe ich einen sehr einfachen PDO-Wrapper erstellt , der eine einzige Datei ist. Es gibt zu zeigen, wie einfach es ist, all die üblichen Dinge zu erledigen, in denen Anwendungen ausgeführt werden müssen. Funktioniert mit PostgreSQL, MySQL und SQLite.

Im Grunde genommen, lesen Sie es , PDO , um zu sehen , wie die PDO - Funktionen setzen im wirklichen Leben zu verwenden , um es einfach zu speichern und abzurufen Werte im Format Sie mögen.

Ich möchte eine einzelne Kolumne

$count = DB::column('SELECT COUNT(*) FROM `user`);

Ich möchte ein Array (key => value) ergebnisse (zB für eine selectbox)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Ich möchte ein einziges Reihenergebnis

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Ich möchte eine Reihe von Ergebnissen

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

In Bezug auf viele nützliche Antworten hoffe ich, diesem Thread einige Werte hinzufügen zu können. Die SQL-Injection ist ein Angriff, der durch Benutzereingaben (Eingaben, die vom Benutzer ausgefüllt und dann in Abfragen verwendet werden) ausgeführt werden kann. Die SQL-Injection-Muster sind korrekte Abfragesyntax, während wir sie aufrufen können: schlechte Abfragen aus schlechten Gründen, wir gehen davon aus, dass dies möglich ist Seien Sie eine schlechte Person, die versucht, (unter Umgehung der Zugangskontrolle) geheime Informationen zu erhalten, die die drei Sicherheitsgrundsätze (Vertraulichkeit, Integrität, Verfügbarkeit) betreffen.

Nun ist es unser Ziel, Sicherheitsbedrohungen wie SQL-Injection-Angriffe zu verhindern, die Frage zu stellen (Wie verhindert man SQL-Injection-Angriffe mit PHP), realistischer zu sein Abfragen, Verwenden von PHP oder einer anderen Programmiersprache ist nicht der Fall oder, wie von mehreren Leuten empfohlen, moderne Technologien wie vorbereitete Anweisungen oder andere Tools zu verwenden, die derzeit den SQL-Injektionsschutz unterstützen, sind diese Tools nicht mehr verfügbar? Wie sichern Sie Ihre Bewerbung?

Mein Ansatz gegen die SQL-Injection ist: das Löschen von Benutzereingabedaten, bevor sie an die Datenbank gesendet werden (bevor sie in einer Abfrage verwendet werden).

Datenfilterung für (Konvertieren unsicherer Daten in sichere Daten) MySQLi Sie, dass PDO und MySQLi nicht verfügbar sind. Wie können Sie Ihre Anwendung MySQLi ? Zwingst du mich, sie zu benutzen? Was ist mit anderen Sprachen als PHP? Ich ziehe es vor, allgemeine Ideen zur Verfügung zu stellen, da sie nicht nur für eine bestimmte Sprache, sondern für eine breitere Grenze verwendet werden können.

  1. SQL-Benutzer (Einschränkung des Benutzerprivilegs): Die gebräuchlichsten SQL-Operationen sind (SELECT, UPDATE, INSERT). Zum Beispiel verwenden Anmeldeseiten und Suchseiten nur SELECT. Warum also DB-Benutzer auf diesen Seiten mit hohen Berechtigungen verwenden? RULE: Erstellen Sie nicht einen Datenbankbenutzer für alle Berechtigungen. Für alle SQL-Vorgänge können Sie Ihr Schema wie (deluser, selectuser, updateuser) als Benutzernamen für eine einfache Verwendung erstellen.

siehe Prinzip des geringsten Privilegs

  1. Datenfilterung: Bevor eine Abfrage erstellt werden kann, sollten Benutzereingaben überprüft und gefiltert werden. Für Programmierer müssen einige Eigenschaften für die einzelnen Benutzereingabevariablen definiert werden: Datentyp, Datenmuster und Datenlänge . Ein Feld, das eine Zahl zwischen (x und y) ist, muss mithilfe einer genauen Regel genau überprüft werden. Für ein Feld, das eine Zeichenfolge (Text) ist: Muster ist der Fall. Beispiel: username darf nur einige Zeichen enthalten. zA-Z0-9_-.] die Länge variiert zwischen (x und n) wobei x und n (ganze Zahlen, x <= n). Regel: Das Erstellen exakter Filter und Validierungsregeln ist für mich die beste Praxis.

  2. Verwenden Sie andere Tools: Hier stimme ich Ihnen auch zu, dass die vorbereitete Anweisung (parametrisierte Abfrage) und gespeicherte Prozeduren die Nachteile hier sind. Diese Möglichkeiten erfordern fortgeschrittene Fähigkeiten, die für die meisten Benutzer nicht vorhanden sind. Die Grundidee hier ist, zwischen SQL-Abfragen zu unterscheiden und die Daten, die darin verwendet werden, können beide Ansätze auch mit unsicheren Daten verwendet werden, da die vom Benutzer eingegebenen Daten der ursprünglichen Abfrage nichts hinzufügen, wie (any oder x = x). Weitere Informationen finden Sie im OWASP SQL Injection Prevention-Datenblatt .

Wenn Sie ein fortgeschrittener Benutzer sind, können Sie diese Verteidigung jetzt wie gewünscht verwenden. Wenn Anfänger jedoch die gespeicherte Prozedur nicht schnell implementieren und die Anweisung vorbereiten können, ist es besser, die Eingabedaten nach Möglichkeit zu filtern.

Lassen Sie uns schließlich berücksichtigen, dass der Benutzer den folgenden Text sendet, anstatt seinen Benutzernamen einzugeben:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Diese Eingabe kann frühzeitig ohne vorbereitete Anweisungen und gespeicherte Prozeduren überprüft werden. Um auf der sicheren Seite zu sein, beginnt deren Verwendung jedoch nach der Filterung und Validierung der Benutzerdaten.

Der letzte Punkt ist das Erkennen eines unerwarteten Verhaltens, das mehr Aufwand und Komplexität erfordert. Es wird nicht für normale Webanwendungen empfohlen. Unerwartetes Verhalten in der obigen Benutzereingabe ist SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, root.

UPDATE1:

Ein Benutzer hat kommentiert, dass dieser Beitrag nutzlos ist, OK! Hier ist , was OWASP.ORG zur Verfügung gestellt:

Hauptabwehrmaßnahmen:

Option # 1: Verwendung vorbereiteter Anweisungen (parametrisierte Abfragen)
Option # 2: Verwendung gespeicherter Prozeduren
Option # 3: Alle vom Benutzer bereitgestellten Eingaben übermitteln

Zusätzliche Abwehrmaßnahmen:

Erzwingen: Wenig Berechtigung
Führen Sie außerdem Folgendes aus: Überprüfung der Whitelist- Eingabe

Wie Sie vielleicht wissen, sollte die Behauptung eines Artikels durch ein gültiges Argument, mindestens eine Referenz, unterstützt werden! Ansonsten wird es als Angriff und schlechte Behauptung betrachtet!

Update2:

Aus dem PHP-Handbuch: PHP: Prepared Statements - Manual :

Flucht und SQL-Injection

Gebundene Variablen werden vom Server automatisch umgangen. Der Server fügt seine Escape-Werte an den entsprechenden Stellen vor der Ausführung in die Anweisungsvorlage ein. Dem Server muss ein Hinweis für den Typ der gebundenen Variablen bereitgestellt werden, um eine entsprechende Konvertierung zu erstellen. Weitere Informationen finden Sie in der Funktion mysqli_stmt_bind_param ().

Das automatische Escapeing von Werten innerhalb des Servers wird manchmal als Sicherheitsfunktion betrachtet, um die SQL-Injektion zu verhindern. Das gleiche Maß an Sicherheit kann mit nicht vorbereiteten Anweisungen erreicht werden, wenn Eingabewerte korrekt maskiert werden.

Update3:

Ich habe Testfälle erstellt, um zu wissen, wie PDO und MySQLi die Abfrage an den MySQL-Server senden, wenn sie die vorbereitete Anweisung verwenden:

PDO:

$user = "''1''"; //Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Abfrageprotokoll:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Abfrageprotokoll:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

Es ist klar, dass eine vorbereitete Anweisung auch den Daten entgeht, sonst nichts.

Wie auch in der obigen Anweisung erwähnt The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly, beweist dies, dass die Datenvalidierung, wie z. B. intval()für Integer-Werte eine gute Idee ist, bevor eine Abfrage gesendet wird, und dass bösartige Benutzerdaten vor dem Senden der Abfrage verhindert werden, dass sie korrekt und gültig sind .

Weitere Informationen finden Sie in dieser Frage: PDO sendet eine Rohabfrage an MySQL, während Mysqli eine vorbereitete Abfrage sendet. Beide Ergebnisse liefern das gleiche Ergebnis

Verweise:

  1. SQL Injection-Spickzettel
  2. SQL-Injektion
  3. Informationssicherheit
  4. Sicherheitsgrundsätze
  5. Datenvalidierung

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

Wenn Sie eine aktuelle Version von PHP verwenden, ist die mysql_real_escape_string Option mysql_real_escape_string nicht mehr verfügbar (obwohl mysqli::escape_string eine moderne Entsprechung ist). In diesen Tagen wäre die Option mysql_real_escape_string nur für älteren Code in einer alten Version von PHP sinnvoll.

Sie haben zwei Möglichkeiten - die Sonderzeichen in Ihrer unsafe_variable oder eine parametrisierte Abfrage zu verwenden. Beides würde Sie vor SQL-Injection schützen. Die parametrisierte Abfrage wird als die bessere Vorgehensweise angesehen, erfordert jedoch vor dem Einsatz eine neuere mysql-Erweiterung in PHP.

Wir werden zuerst die untere Aufprallschnur abdecken.

//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 Funktion mysql_real_escape_string .

Um die parametrisierte Abfrage verwenden zu können, müssen MySQLi anstelle der MySQL Funktionen MySQLi verwenden. Um Ihr Beispiel umzuschreiben, benötigen wir Folgendes.

<?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 mysqli::prepare möchten, wäre mysqli::prepare .

Wie andere vorgeschlagen haben, kann es auch hilfreich sein, eine Abstraktionsebene mit etwas wie PDO .

Bitte beachten Sie, dass der Fall, den Sie angefragt haben, ziemlich einfach ist und dass komplexere Fälle möglicherweise komplexere Ansätze erfordern. Im Speziellen:

  • Wenn Sie die SQL-Struktur basierend auf Benutzereingaben ändern möchten, helfen parametrisierte Abfragen nicht, und die erforderliche mysql_real_escape_string wird nicht von mysql_real_escape_string abgedeckt. In diesem Fall können Sie die Eingabe des Benutzers besser über eine Whitelist durchführen, um sicherzustellen, dass nur "sichere" Werte durchgelassen werden.
  • Wenn Sie Ganzzahlen aus Benutzereingaben in einer Bedingung verwenden und den Ansatz mysql_real_escape_string , leiden Sie unter dem von Polynomial in den folgenden Kommentaren beschriebenen Problem. Dieser Fall ist komplizierter, da Ganzzahlen nicht in Anführungszeichen gesetzt werden. Sie können also prüfen, ob die Benutzereingabe nur Ziffern enthält.
  • Es gibt wahrscheinlich andere Fälle, die mir nicht bekannt sind. Möglicherweise ist this eine nützliche Ressource für einige der subtileren Probleme, auf die Sie stoßen können.

WICHTIG

Die beste Möglichkeit, SQL Injection zu verhindern, ist die Verwendung von Prepared Statements anstelle von Escaping , wie die akzeptierte Antwort zeigt.

Es gibt Bibliotheken wie Aura.Sql und EasyDB , mit denen Entwickler vorbereitete Anweisungen einfacher verwenden können. Weitere Informationen dazu, warum vorbereitete Anweisungen die SQL-Injection besser stoppen können , finden Sie in diesem mysql_real_escape_string() Umgehung von mysql_real_escape_string() und vor kurzem behobenen Unicode-SQL-Injection-Schwachstellen in WordPress .

Injektionsprävention - mysql_real_escape_string()

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

mysql_real_escape_string nimmt eine Zeichenfolge, die in einer MySQL-Abfrage verwendet wird, und gibt dieselbe Zeichenfolge mit allen SQL-Injection-Versuchen zurück, die sicher mit Escapezeichen versehen sind. Im Wesentlichen werden die problematischen Anführungszeichen ('), die ein Benutzer eingeben kann, durch einen MySQL-sicheren Ersatz ersetzt, ein Escape-Zitat \'.

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

// Verbinden Sie sich mit MySQL

$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;

Weitere Informationen finden Sie in MySQL - SQL Injection Prevention .


Es gibt so viele Antworten für PHP und MySQL , aber hier ist Code für PHP und Oracle zur Verhinderung der SQL-Injektion sowie der regelmäßigen Verwendung von oci8-Treibern:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

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

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

Durch die Verwendung von mysql_ anstelle von mysql_ , mysqli_ und pgsql_ Funktionen machen Sie Ihre App etwas mehr von der Datenbank abstrahiert, in dem seltenen mysqli_ , dass Sie den Datenbankanbieter wechseln müssen.


Jede Antwort hier behandelt nur einen Teil des Problems. Tatsächlich gibt es vier verschiedene Abfrageteile, die wir dynamisch hinzufügen können:

  • ein Faden
  • eine Zahl
  • eine Kennung
  • ein Schlüsselwort für die Syntax.

Und vorbereitete Aussagen betreffen nur zwei davon.

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

Im Allgemeinen basiert ein solcher Schutzansatz auf Whitelisting .

In diesem Fall sollte jeder dynamische Parameter in Ihrem Skript fest codiert und aus diesem Satz ausgewählt werden. So können Sie beispielsweise eine dynamische Bestellung durchführen:

$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 eine andere Möglichkeit, Identifikatoren zu sichern - das Fluchtzeichen. Solange Sie einen Bezeichner angeben, können Sie Backticks durch Verdoppeln entgehen.

Als einen weiteren Schritt können wir uns eine wirklich brillante Idee der Verwendung eines Platzhalters (ein Proxy, der den tatsächlichen Wert in der Abfrage darstellt) aus den vorbereiteten Anweisungen leihen und einen Platzhalter eines anderen Typs - einen Bezeichnerplatzhalter - erfinden.

Um es kurz zu machen: Es handelt sich um einen Platzhalter , eine nicht vorbereitete Aussage kann als eine silberne Kugel betrachtet werden.

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

Es gibt jedoch ein Problem mit SQL-Syntaxschlüsselwörtern (wie AND , DESC ), aber in diesem Fall scheint White-Listing der einzige Ansatz zu sein.

Aktualisieren

Obwohl allgemein Einigkeit über die bewährten Vorgehensweisen zum Schutz der SQL-Injection besteht, gibt es auch noch viele schlechte Vorgehensweisen. Und einige von ihnen waren zu tief in den Köpfen der PHP-Benutzer verankert. Zum Beispiel gibt es auf dieser Seite (obwohl für die meisten Besucher unsichtbar) mehr als 80 gelöschte Antworten - alle wurden von der Community aufgrund schlechter Qualität oder aufgrund schlechter Praktiken entfernt. Schlimmer noch, einige der schlechten Antworten werden nicht gelöscht, sondern es geht ihnen gut.

Zum Beispiel there(1) are(2) still(3) many(4) answers(5) , einschließlich der share die Ihnen ein manuelles String-Escaping nahe share - ein überholter Ansatz, der sich als unsicher erwiesen hat.

Oder es gibt eine etwas bessere Antwort, die nur eine andere Methode der String-Formatierung vorschlägt und sogar das ultimative Allheilmittel darstellt. Das ist es natürlich nicht. Diese Methode ist nicht besser als die normale String-Formatierung, behält jedoch alle ihre Nachteile: Sie gilt nur für Strings und ist wie jede andere manuelle Formatierung im Wesentlichen optional, nicht obligatorisch und anfällig für menschliche Fehler jeglicher Art.

Ich denke, das alles wegen eines sehr alten Aberglaubens, der von solchen Behörden wie OWASP oder dem PHP-Handbuch unterstützt wird , in dem Gleichheit zwischen dem "Flüchten" und dem Schutz vor SQL-Injektionen ausgerufen wird.

Unabhängig davon, was das PHP-Handbuch seit Jahrhunderten sagt, *_escape_string macht auf keinen Fall Daten sicher und war niemals dazu gedacht. Manuelles Escaping ist nicht nur für SQL-Teile außer String nützlich, sondern auch falsch, da es manuell und nicht automatisiert ist.

Und OWASP macht es noch schlimmer, da es darauf ankommt, den Benutzereingaben zu entgehen, was ein absoluter Blödsinn ist: Im Zusammenhang mit dem Injektionsschutz sollte es solche Wörter nicht geben. Jede Variable ist potentiell gefährlich - unabhängig von der Quelle! Oder anders ausgedrückt: Jede Variable muss richtig formatiert sein, um in eine Abfrage eingefügt zu werden - unabhängig von der Quelle. Auf das Ziel kommt es an. In dem Moment, in dem ein Entwickler beginnt, die Schafe von den Ziegen zu trennen (wobei er überlegt, ob eine bestimmte Variable "sicher" ist oder nicht), macht er / sie den ersten Schritt in Richtung einer Katastrophe. Ganz zu schweigen davon, dass sogar der Wortlaut ein Massenaustritt am Einstiegspunkt nahelegt, das dem sehr magischen Anführungszeichen ähnelt - bereits verachtet, abgelehnt und entfernt.

Im Gegensatz zu allem, was "Flucht" ist, sind vorbereitete Anweisungen das Maß, das tatsächlich vor SQL-Injection schützt (falls zutreffend).

Wenn Sie immer noch nicht überzeugt sind, finden Sie hier eine Schritt-für-Schritt-Erklärung, den Hitchhiker-Leitfaden für die Prävention von SQL-Injektionen . Dort habe ich all diese Dinge ausführlich erklärt und sogar einen Abschnitt zusammengestellt, der sich ausschließlich mit schlechten Praktiken und deren Offenlegung befasst.


Eine gute Idee ist die Verwendung eines 'objektrelationalen Mapper' wie Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Es erspart Ihnen nicht nur SQL-Injektionen, sondern auch Syntaxfehler! Unterstützt auch Modellsammlungen mit Methodenverkettung zum Filtern oder Anwenden von Aktionen auf mehrere Ergebnisse gleichzeitig und mehrere Verbindungen.


Es gibt viele Möglichkeiten, SQL-Injektionen und andere SQL-Hacks zu verhindern. Sie können es leicht im Internet finden (Google-Suche). Natürlich ist PDO eine der guten Lösungen. Aber ich möchte Ihnen einige gute Links vorbeugen, die durch SQL Injection verhindert werden.

Was ist SQL-Injection und wie kann man das verhindern?

PHP-Handbuch zur SQL-Injection

Microsoft Erklärung der SQL-Injection und -Vermeidung in PHP

und einige andere wie SQL-Injection mit MySQL und PHP verhindern

Nun, warum tun Sie Sie benötigen , um Ihre Abfrage von SQL - Injection zu verhindern?

Ich möchte Sie wissen lassen: Warum versuchen wir die SQL-Injektion mit einem kurzen Beispiel zu verhindern:

Abfrage zur Übereinstimmung der Anmeldeauthentifizierung:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Nun, wenn jemand (ein Hacker) setzt

$_POST['email']= [email protected]' OR '1=1

und Passwort alles ....

Die Abfrage wird nur bis zu:

$query="select * from users where email='[email protected]' OR '1=1';

Der andere Teil wird verworfen. Was wird also passieren? Ein nicht autorisierter Benutzer (Hacker) kann sich ohne sein Passwort als Administrator anmelden. Jetzt kann er alles tun, was der Administrator / E-Mail-Mitarbeiter tun kann. Es ist sehr gefährlich, wenn die SQL-Injection nicht verhindert wird.


Wie Sie sehen, schlagen die Leute vor, dass Sie höchstens vorbereitete Aussagen verwenden. Das ist nicht falsch, aber wenn Ihre Abfrage nur einmal pro Prozess ausgeführt wird, würde dies zu einer leichten Leistungseinbusse führen.

Ich war mit diesem Problem konfrontiert, aber ich denke, ich habe es auf raffinierte Weise gelöst - die Art, wie Hacker die Verwendung von Anführungszeichen vermeiden. Ich habe dies in Verbindung mit emulierten vorbereiteten Aussagen verwendet. Ich verwende es, um alle möglichen SQL-Injection-Angriffe zu verhindern.

Mein Ansatz:

  • Wenn Sie erwarten, dass die Eingabe eine Ganzzahl ist, stellen Sie sicher, dass sie wirklich ganzzahlig ist. In einer Sprache mit variablen Typen wie PHP ist dies sehr wichtig. Sie können beispielsweise diese sehr einfache, aber leistungsfähige Lösung verwenden: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Wenn Sie etwas anderes von Integer- Hex erwarten. Wenn Sie es verhexen, entziehen Sie sich jeder Eingabe. In C / C ++ gibt es eine Funktion namens mysql_hex_string() , in PHP können Sie bin2hex() .

    Machen Sie sich keine Sorgen, dass der maskierte String eine doppelte Größe der ursprünglichen Länge hat, da PHP selbst bei Verwendung von mysql_real_escape_string dieselbe Kapazität ((2*input_length)+1) , was dieselbe ist.

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

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 spritzen.

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

Es gab einige Diskussionen in den Kommentaren, deshalb möchte ich es endlich klarstellen. Diese beiden Ansätze sind sehr ähnlich, unterscheiden sich jedoch in mancher Hinsicht ein wenig:

Das Präfix ** 0x ** kann nur für Datenspalten wie char, varchar, text, block, binary usw. verwendet werden .
Die Verwendung ist etwas kompliziert, wenn Sie gerade eine leere Zeichenkette einfügen. Sie müssen es vollständig durch '' ersetzen, oder Sie erhalten eine Fehlermeldung.

UNHEX () funktioniert in jeder Spalte. Sie müssen sich nicht um die leere Zeichenfolge kümmern.

Hex-Methoden werden oft als Angriffe verwendet

Beachten Sie, dass diese Hex-Methode häufig als SQL-Injection-Angriff verwendet wird, bei dem Ganzzahlen wie Strings dargestellt und nur mit mysql_real_escape_string mit mysql_real_escape_string . Dann können Sie die Verwendung von Anführungszeichen vermeiden.

Wenn Sie zum Beispiel so etwas tun:

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

ein Angriff kann Sie sehr leicht spritzen. Betrachten Sie den folgenden eingefügten Code, der von Ihrem Skript zurückgegeben wurde:

SELECT ... WHERE id = -1 union all select table_name aus information_schema.tables

und extrahieren Sie jetzt einfach die Tabellenstruktur:

SELECT ... WHERE id = -1 union all select spaltenname aus information_schema.column mit tabellenname = 0x61727469636c65

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

Wenn der Codierer einer injizierbaren Stelle jedoch hexen würde, wäre keine Injektion möglich, da die Abfrage folgendermaßen aussehen würde: SELECT ... WHERE id = UNHEX('2d312075...3635')


Mit dieser PHP-Funktion können mysql_escape_string()Sie schnell eine gute Prävention erhalten.

Zum Beispiel:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - Escape ein String zur Verwendung in einer mysql_query

Für mehr Prävention können Sie am Ende hinzufügen ...

wHERE 1=1   or  LIMIT 1

Endlich bekommst du:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

Einige Richtlinien zum Umgehen von Sonderzeichen in SQL-Anweisungen.

Verwenden Sie nicht MySQL , diese Erweiterung ist veraltet, verwenden Sie MySQLi oder PDO .

MySQLi

Um Sonderzeichen in einer Zeichenfolge manuell zu mysqli_real_escape_string können Sie die Funktion mysqli_real_escape_string verwenden. Die Funktion funktioniert nicht ordnungsgemäß, es sei denn, der richtige Zeichensatz wird mit mysqli_set_charset .

Beispiel:

$mysqli = new mysqli( 'host', 'user', 'password', 'database' );
$mysqli->set_charset( 'charset');

$string = $mysqli->real_escape_string( $string );
$mysqli->query( "INSERT INTO table (column) VALUES ('$string')" );

Verwenden mysqli_prepare zum automatischen mysqli_prepare von Werten mit vorbereiteten Anweisungen mysqli_prepare und mysqli_stmt_bind_param wobei für eine entsprechende Konvertierung Typen für die entsprechenden Bind-Variablen angegeben werden müssen:

Beispiel:

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

$stmt->bind_param( "is", $integer, $string );

$stmt->execute();

Egal ob Sie vorbereitete Anweisungen oder mysqli_real_escape_string verwenden, Sie müssen immer wissen, mit welchen Eingabedaten Sie arbeiten.

Wenn Sie also eine vorbereitete Anweisung verwenden, müssen Sie die Variablentypen für die Funktion mysqli_stmt_bind_param angeben.

Und der Gebrauch von mysqli_real_escape_string ist, wie der Name schon sagt, Sonderzeichen in einem String zu umgehen, so dass Integer nicht sicher werden. Mit dieser Funktion soll verhindert werden, dass die Zeichenfolgen in SQL-Anweisungen beschädigt werden und die Datenbank dadurch beschädigt wird. mysqli_real_escape_string ist eine nützliche Funktion, wenn sie ordnungsgemäß verwendet wird, insbesondere wenn sie mit sprintf kombiniert wird.

Beispiel:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

Ich habe diese kleine Funktion vor einigen Jahren geschrieben:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Dies ermöglicht das Ausführen von Anweisungen in einem einzeiligen C # -ish String.Format wie:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Es entgeht dem Variablentyp. Wenn Sie versuchen, Tabellen- und Spaltennamen zu parametrisieren, schlägt dies fehl, da jede Zeichenfolge in Anführungszeichen gesetzt wird. Dies ist eine ungültige Syntax.

SICHERHEITS-UPDATE: Die vorherige str_replaceVersion erlaubte das Hinzufügen von {#} -Token zu den Benutzerdaten. Diese preg_replace_callbackVersion verursacht keine Probleme, wenn der Ersatz diese Token enthält.


** Warnung: Der in dieser Antwort beschriebene Ansatz gilt nur für sehr spezifische Szenarien und ist nicht sicher, da SQL-Injection-Angriffe nicht nur von Injektionen abhängig sind X=Y. **

Wenn die Angreifer versuchen, sich über die PHP- $_GETVariable oder mit der Abfragezeichenfolge der URL in das Formular zu hacken , können Sie sie fangen, wenn sie nicht sicher sind.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Denn 1=1, 2=2, 1=2, 2=1, 1+1=2, etc ... sind die häufigsten Fragen zu einer SQL - Datenbank eines Angreifers. Vielleicht wird es auch von vielen Hacking-Anwendungen verwendet.

Sie müssen jedoch vorsichtig sein, dass Sie eine sichere Abfrage von Ihrer Site nicht neu schreiben dürfen. Der obige Code gibt Ihnen den Tipp, diese hacking-spezifische dynamische Abfragezeichenfolge in eine Seite zu schreiben , von der die IP-Adresse des Angreifers gespeichert wird , oder von EVE IHRE COOKIES, dem Verlauf, dem Browser oder anderen sensiblen Objekten (je nach Ihnen) Informationen, damit Sie später mit ihnen umgehen können, indem Sie ihr Konto sperren oder Behörden kontaktieren.


Beginnen wir mit dem Standardkommentar, den wir allen geben:

share . Sie werden nicht mehr gewartet deprecated . Sehen Sie die rote Box ? MySQLi Sie sich stattdessen über vorbereitete Anweisungen und verwenden Sie PDO oder MySQLi Dieser Artikel hilft Ihnen bei der Entscheidung. Wenn Sie sich für PDO entscheiden, finden Sie hier eine gute Anleitung .

Gehen wir das Satz für Satz durch und erklären Sie:

  • Sie werden nicht mehr gewartet und sind offiziell veraltet

    Dies bedeutet, dass die PHP-Community die Unterstützung für diese sehr alten Funktionen allmählich einstellt. Sie werden wahrscheinlich in einer zukünftigen (aktuellen) Version von PHP nicht existieren! Die fortgesetzte Verwendung dieser Funktionen kann Ihren Code in (nicht so weiter) Zukunft beschädigen.

    NEU! - ext / mysql ist nun deprecated

    Neuer ext / mysql wurde in PHP 7 entfernt .

  • Stattdessen sollten Sie von vorbereiteten Aussagen erfahren

    mysql_* -Erweiterung unterstützt keine vorbereiteten Anweisungen. mysql_* ist (unter anderem) eine sehr effektive Gegenmaßnahme gegen die SQL-Injektion . Es wurde eine sehr schwere Sicherheitslücke in MySQL-abhängigen Anwendungen behoben, durch die Angreifer Zugriff auf Ihr Skript erhalten und alle möglichen Abfragen in Ihrer Datenbank durchführen können.

    Weitere Informationen finden Sie unter Wie kann ich die SQL-Injection in PHP verhindern?

  • Sehen Sie die rote Box?

    Wenn Sie zu einer mysql Funktionshandbuch-Seite mysql , wird ein rotes Kästchen angezeigt, das erklärt, es sollte nicht mehr verwendet werden.

  • Verwenden Sie entweder PDO oder MySQLi

    Es gibt bessere, robustere und besser aufgebaute Alternativen, PDO , die einen vollständigen OOP-Ansatz für die Datenbankinteraktion MySQLi , und MySQLi , eine MySQL-spezifische Verbesserung.





php mysql sql security sql-injection