Wie kann ich die SQL-Injection in PHP verhindern?

2773

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

$unsafe_variable = $_POST['user_input']; 

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

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

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

Was kann getan werden, um dies zu verhindern?

Andrew G. Johnson
quelle

Antworten:

8959

Verwenden Sie vorbereitete Anweisungen und parametrisierte Abfragen. Hierbei handelt es sich um SQL-Anweisungen, die getrennt von Parametern an den Datenbankserver gesendet und von diesem analysiert werden. Auf diese Weise kann ein Angreifer kein schädliches SQL einschleusen.

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

  1. Verwenden 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. Verwenden 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 in eine andere Datenbank als MySQL sich verbinden, gibt es einen Treiber spezifische zweite Option , die Sie sich beziehen können (zum Beispiel pg_prepare()und pg_execute()für PostgreSQL). PDO ist die universelle Option.


Richtiges Einrichten der Verbindung

Beachten Sie, dass beim PDOZugriff auf eine MySQL-Datenbank standardmäßig nicht wirklich vorbereitete Anweisungen verwendet werden . 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, es wird jedoch empfohlen, ihn hinzuzufügen . Auf diese Weise hört das Skript nicht mit einem auf, Fatal Errorwenn etwas schief geht. Und es gibt dem Entwickler die Möglichkeit, catchFehler zu machen, die thrown wie PDOExceptions sind.

Was jedoch obligatorisch ist, ist die erste setAttribute()Zeile, in der PDO angewiesen wird, emulierte vorbereitete Anweisungen zu deaktivieren und echte vorbereitete Anweisungen zu verwenden. Dadurch wird sichergestellt, dass die Anweisung und die Werte nicht von PHP analysiert werden, bevor sie an den MySQL-Server gesendet werden (sodass ein möglicher Angreifer keine Chance hat, schädliches SQL einzuschleusen).

Obwohl Sie die charsetin den Optionen des Konstruktors festlegen können , ist es wichtig zu beachten, dass "ältere" Versionen von PHP (vor 5.3.6) den Zeichensatzparameter im DSN stillschweigend ignorierten .


Erläuterung

Die SQL-Anweisung, an die Sie übergeben, preparewird vom Datenbankserver analysiert und kompiliert. Durch Angabe von Parametern (entweder ein ?oder ein benannter Parameter wie :nameim obigen Beispiel) teilen Sie dem Datenbankmodul mit, nach was Sie filtern möchten. Wenn Sie dann aufrufen execute, wird die vorbereitete Anweisung mit den von Ihnen angegebenen Parameterwerten kombiniert.

Wichtig hierbei ist, dass die Parameterwerte mit der kompilierten Anweisung kombiniert werden, nicht mit einer SQL-Zeichenfolge. Bei der SQL-Injection wird das Skript dazu verleitet, beim Erstellen von SQL zum Senden an die Datenbank schädliche Zeichenfolgen einzuschließen. Indem Sie das eigentliche SQL getrennt von den Parametern senden, begrenzen Sie das Risiko, dass Sie etwas erhalten, das Sie nicht beabsichtigt haben.

Alle Parameter, die Sie bei Verwendung einer vorbereiteten Anweisung senden, werden nur als Zeichenfolgen behandelt (obwohl das Datenbankmodul möglicherweise einige Optimierungen vornimmt, sodass Parameter natürlich auch als Zahlen enden können). Wenn die $nameVariable im obigen Beispiel 'Sarah'; DELETE FROM employeesdas Ergebnis enthält, wäre dies einfach eine Suche nach der Zeichenfolge "'Sarah'; DELETE FROM employees", und Sie erhalten keine leere Tabelle .

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

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

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 speziellen 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';
}
PeeHaa
quelle
86
Nur um hinzuzufügen, weil ich es hier nirgendwo anders gesehen habe, ist eine andere Verteidigungslinie eine Webanwendungs-Firewall (WAF), für die Regeln festgelegt werden können, um nach SQL-Injection-Angriffen zu suchen:
Jkerak
37
Außerdem erlaubt die offizielle Dokumentation von mysql_query nur die Ausführung einer Abfrage, also jede andere Abfrage außer; wird ignoriert. Auch wenn dies bereits veraltet ist, gibt es unter PHP 5.5.0 viele Systeme, die diese Funktion möglicherweise verwenden. php.net/manual/en/function.mysql-query.php
Randall Valenciano
12
Dies ist eine schlechte Angewohnheit, aber eine Lösung nach dem Problem: Nicht nur für die SQL-Injection, sondern für jede Art von Injection (zum Beispiel gab es in F3 Framework v2 ein Loch für die Injektion von Ansichtsvorlagen), wenn Sie über eine fertige alte Website oder App leiden Bei Injektionsfehlern besteht eine Lösung darin, die Werte Ihrer supperglobalen vordefinierten Variablen wie $ _POST mit Escape-Werten beim Bootstrap neu zuzuweisen. Mit PDO ist es immer noch möglich zu entkommen (auch für heutige Frameworks): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix
15
Dieser Antwort fehlt die Erklärung, was eine vorbereitete Aussage ist - eine Sache - es ist ein Leistungseinbruch, wenn Sie während Ihrer Anfrage viele vorbereitete Aussagen verwenden und manchmal 10x Leistungseinbußen ausmachen. Besser wäre die Verwendung von PDO mit deaktivierter Parameterbindung, aber deaktivierter Anweisungsvorbereitung.
Donis
6
Die Verwendung von PDO ist besser, falls Sie eine direkte Abfrage verwenden, stellen Sie sicher, dass Sie mysqli :: Escape_string
Kassem Itani
1652

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

Sicherheitswarnung : Diese Antwort entspricht nicht den bewährten Sicherheitsmethoden. Das Escaping ist nicht ausreichend, um eine SQL-Injection zu verhindern . Verwenden Sie stattdessen vorbereitete Anweisungen . Verwenden Sie die unten beschriebene Strategie auf eigenes Risiko. ( mysql_real_escape_string()Wurde auch in PHP 7 entfernt.)

Wenn Sie eine neuere Version von PHP verwenden, ist die mysql_real_escape_stringunten beschriebene Option nicht mehr verfügbar (obwohl dies mysqli::escape_stringein modernes Äquivalent ist). Heutzutage ist die mysql_real_escape_stringOption nur für Legacy-Code in einer alten Version von PHP sinnvoll.


Sie haben zwei Möglichkeiten - das Ausblenden der Sonderzeichen in Ihrer unsafe_variableoder die Verwendung einer parametrisierten Abfrage. Beides würde Sie vor SQL-Injection schützen. Die parametrisierte Abfrage wird als die bessere Vorgehensweise angesehen, erfordert jedoch den Wechsel zu einer neueren MySQL-Erweiterung in PHP, bevor Sie sie verwenden können.

Wir werden zuerst die untere Aufprallschnur abdecken, die einer entweicht.

//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_stringFunktion.

Um die parametrisierte Abfrage zu verwenden, müssen Sie MySQLi anstelle der MySQL- Funktionen verwenden. Um Ihr Beispiel neu zu schreiben, 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 nachlesen möchten, wäre mysqli::prepare.

Wie andere vorgeschlagen haben, ist es möglicherweise nützlich / einfacher, eine Abstraktionsebene mit etwas wie PDO zu verstärken .

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

  • Wenn Sie die Struktur von SQL basierend auf Benutzereingaben ändern möchten, helfen parametrisierte Abfragen nicht weiter, und das erforderliche Escape wird von nicht abgedeckt mysql_real_escape_string. In diesem Fall ist es besser, wenn Sie die Benutzereingaben über eine Whitelist weiterleiten, um sicherzustellen, dass nur "sichere" Werte zugelassen werden.
  • Wenn Sie in einer Bedingung Ganzzahlen aus Benutzereingaben verwenden und den mysql_real_escape_stringAnsatz wählen, leiden Sie unter dem von Polynomial in den Kommentaren unten beschriebenen Problem . Dieser Fall ist schwieriger, da Ganzzahlen nicht in Anführungszeichen gesetzt werden. Sie können also damit umgehen, indem Sie überprüfen, ob die Benutzereingabe nur Ziffern enthält.
  • Es gibt wahrscheinlich andere Fälle, die mir nicht bekannt sind. Möglicherweise ist dies eine nützliche Ressource für einige der subtileren Probleme, auf die Sie stoßen können.
Matt Sheppard
quelle
1
Verwenden mysql_real_escape_stringist genug oder muss ich auch parametrisiert verwenden?
Peiman F.
5
@peimanF. Verwenden Sie auch bei einem lokalen Projekt eine bewährte Methode zur Verwendung parametrisierter Abfragen. Bei parametrisierten Abfragen wird garantiert, dass keine SQL-Injection erfolgt. Denken Sie jedoch daran, dass Sie die Daten htmlentities
bereinigen
2
@peimanF. Gute Praxis für parametrisierte Abfragen und Bindungswerte, aber echte Escape-Zeichenfolge ist
Richard
Ich verstehe die Einbeziehung der mysql_real_escape_string()Vollständigkeit halber, bin aber kein Fan davon, zuerst den fehleranfälligsten Ansatz aufzulisten. Der Leser könnte sich schnell das erste Beispiel schnappen. Gut, dass es jetzt veraltet ist :)
Steen Schütt
1
@ SteenSchütt - Alle mysql_*Funktionen sind veraltet. Sie wurden durch ähnliche mysqli_* Funktionen ersetzt, wie z mysqli_real_escape_string.
Rick James
1073

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

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

Und vorbereitete Aussagen decken nur zwei davon ab.

Manchmal müssen wir unsere Abfrage jedoch noch dynamischer gestalten und auch Operatoren oder Bezeichner hinzufügen. Wir brauchen also verschiedene Schutztechniken.

Im Allgemeinen basiert ein solcher Schutzansatz auf einer Whitelist .

In diesem Fall sollte jeder dynamische Parameter in Ihrem Skript fest codiert und aus diesem Satz 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 vereinfachen, habe ich eine Whitelist-Hilfsfunktion geschrieben , die alle Aufgaben 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 einen anderen Weg, um Identifikatoren zu sichern - das Entkommen, aber ich bleibe lieber bei der Whitelist als einem robusteren und expliziteren Ansatz. Solange Sie jedoch eine Kennung in Anführungszeichen haben, können Sie das Anführungszeichen aus Sicherheitsgründen umgehen. Beispielsweise müssen Sie für MySQL standardmäßig das Anführungszeichen verdoppeln, um es zu umgehen . Für andere DBMS-Escape-Regeln wären die Regeln anders.

Dennoch gibt es ein Problem mit SQL - Syntax Schlüsselwort (wie AND, DESCund so weiter ), aber Whitelisting scheint den einzigen Ansatz in diesem Fall.

Eine allgemeine Empfehlung kann also wie folgt formuliert werden

  • Jede Variable, die ein SQL-Datenliteral darstellt (oder einfach ausgedrückt eine SQL-Zeichenfolge oder eine Zahl), muss über eine vorbereitete Anweisung hinzugefügt werden. Keine Ausnahmen.
  • Alle anderen Abfrageteile, z. B. ein SQL-Schlüsselwort, eine Tabelle oder ein Feldname oder ein Operator, müssen durch eine weiße Liste gefiltert werden.

Aktualisieren

Obwohl es eine allgemeine Übereinstimmung über die Best Practices für den SQL-Injection-Schutz gibt, gibt es immer noch viele schlechte Praktiken. Und einige von ihnen sind zu tief in den Köpfen der PHP-Benutzer verwurzelt. Zum Beispiel gibt es auf dieser Seite (obwohl für die meisten Besucher unsichtbar) mehr als 80 gelöschte Antworten, die alle von der Community aufgrund schlechter Qualität oder der Förderung schlechter und veralteter Praktiken entfernt wurden. Schlimmer noch, einige der schlechten Antworten werden nicht gelöscht, sondern gedeihen.

Zum Beispiel gibt es (1) (2) immer noch (3) viele (4) Antworten (5) , einschließlich der am zweithäufigsten bewerteten Antwort, die darauf hindeutet, dass Sie manuell Zeichenfolgen entfernen - ein veralteter Ansatz, der sich als unsicher erwiesen hat.

Oder es gibt eine etwas bessere Antwort, die nur eine andere Methode zur Formatierung von Zeichenfolgen vorschlägt und diese sogar als ultimatives Allheilmittel bezeichnet. Während es natürlich nicht ist. Diese Methode ist nicht besser als die normale Formatierung von Zeichenfolgen, behält jedoch alle Nachteile bei: Sie gilt nur für Zeichenfolgen und ist wie jede andere manuelle Formatierung im Wesentlichen eine optionale, nicht obligatorische Maßnahme, die für menschliches Versagen jeglicher Art anfällig ist.

Ich denke, dass dies alles auf einen sehr alten Aberglauben zurückzuführen ist, der von Behörden wie OWASP oder dem PHP-Handbuch unterstützt wird und die Gleichheit zwischen "Entkommen" und Schutz vor SQL-Injektionen proklamiert.

Unabhängig davon, was das PHP-Handbuch seit Ewigkeiten sagt, *_escape_stringmacht es Daten auf keinen Fall sicher und war nie dazu gedacht. Manuelles Escape ist nicht nur für andere SQL-Teile als Zeichenfolgen nutzlos, sondern auch falsch, da es manuell und nicht automatisiert ist.

Und OWASP macht es noch schlimmer, indem es betont, dass Benutzereingaben vermieden werden, was ein völliger Unsinn ist: Im Zusammenhang mit dem Injektionsschutz sollte es keine solchen Worte geben. Jede Variable ist potenziell gefährlich - unabhängig von der Quelle! Mit anderen Worten - 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 (er überlegt, ob eine bestimmte Variable "sicher" ist oder nicht), unternimmt er seinen ersten Schritt in Richtung Katastrophe. Ganz zu schweigen davon, dass selbst der Wortlaut darauf hindeutet, dass die Masse am Einstiegspunkt entweicht und der Funktion der magischen Anführungszeichen ähnelt - bereits verachtet, veraltet und entfernt.

Also, im Gegensatz zu, was auch immer „Flucht“, Prepared Statements ist die Maßnahme , dass in der Tat von SQL - Injection schützt (wenn zutreffend).

Ihr gesunder Menschenverstand
quelle
848

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.

Und von PDO mit nicht mysql_, mysqli_und pgsql_Funktionen, machen Sie Ihre Anwendung ein wenig mehr aus der Datenbank abstrahiert, in seltenen Vorkommen , dass Sie Switch - Datenbank - Anbieter haben.

Kibbee
quelle
619

Verwenden PDOund vorbereitete Abfragen.

( $connist ein PDOObjekt)

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

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

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

Mein Ansatz:

  • Wenn Sie erwarten, dass die Eingabe eine Ganzzahl ist, stellen Sie sicher, dass sie wirklich eine Ganzzahl ist. In einer variablen Sprache wie PHP ist dies 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 Hex erwarten . Wenn Sie es verhexen, entziehen Sie sich perfekt allen Eingaben. In C / C ++ gibt es eine Funktion namens mysql_hex_string(), in PHP können Sie verwenden bin2hex().

    Machen Sie sich keine Sorgen, dass die maskierte Zeichenfolge die doppelte Größe ihrer ursprünglichen Länge hat, denn selbst wenn Sie sie verwenden mysql_real_escape_string, muss PHP dieselbe Kapazität zuweisen ((2*input_length)+1), die dieselbe ist.

  • Diese Hex-Methode wird häufig verwendet, wenn Sie Binärdaten übertragen. Ich sehe jedoch keinen Grund, sie nicht für alle Daten zu verwenden, um SQL-Injection-Angriffe zu verhindern. Beachten Sie, dass Sie Daten mit 0xder MySQL-Funktion voranstellen oder UNHEXstattdessen verwenden müssen.

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, deshalb möchte ich es endlich klarstellen. Diese beiden Ansätze sind sehr ähnlich, unterscheiden sich jedoch in einigen Punkten geringfügig:

Das Präfix ** 0x ** kann nur für Datenspalten wie char, varchar, text, block, binary usw. verwendet werden .
Außerdem ist die Verwendung etwas kompliziert, wenn Sie eine leere Zeichenfolge einfügen möchten. Sie müssen es vollständig ersetzen, sonst ''wird eine Fehlermeldung angezeigt.

UNHEX () funktioniert für jede Spalte. Sie müssen sich keine Sorgen um die leere Zeichenfolge machen.


Hex-Methoden werden häufig als Angriffe verwendet

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

Zum Beispiel, wenn Sie einfach so etwas tun:

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

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

SELECT ... WHERE id = -1 union all wählt table_name aus information_schema.tables aus

und jetzt einfach die Tabellenstruktur extrahieren:

SELECT ... WHERE id = -1 union all wählt Spaltenname aus information_schema.column aus, wobei table_name = 0x61727469636c65

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

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

Zaffy
quelle
6
@ SumitGupta Ja, du hast es getan. MySQL verkettet nicht mit, +sondern mit CONCAT. Und zur Leistung: Ich glaube nicht, dass dies die Leistung beeinträchtigt, da MySQL Daten analysieren muss und es keine Rolle spielt, ob der Ursprung ein String oder ein Hex ist
Zaffy
11
@YourCommonSense Du verstehst das Konzept nicht ... Wenn du einen String in MySQL haben willst, zitierst du ihn so 'root'oder du kannst ihn 0x726f6f74hexen , ABER wenn du eine Zahl willst und sie als String schickst, wirst du wahrscheinlich '42' schreiben, nicht CHAR (42) ) ... '42' in hex wäre 0x3432nicht0x42
Zaffy
7
@YourCommonSense Ich habe nichts zu sagen ... nur lol ... wenn Sie immer noch Hex auf numerischen Feldern versuchen möchten, siehe zweiten Kommentar. Ich wette mit dir, dass es funktionieren wird.
Zaffy
2
Die Antwort besagt eindeutig, dass dies mit ganzzahligen Werten nicht funktioniert. Der Grund dafür ist, dass bin2hex den übergebenen Wert in eine Zeichenfolge konvertiert (und somit bin2hex (0) 0x30 und nicht 0x03 ist) - das ist wahrscheinlich der Teil, der Sie verwirrt . Wenn Sie dem folgen, funktioniert es perfekt (zumindest auf meiner Website, getestet mit 4 verschiedenen MySQL-Versionen auf Debian-Computern, 5.1.x bis 5.6.x). Hexadezimal ist schließlich nur die Art der Darstellung, nicht der Wert;)
Griffin
5
@YourCommonSense verstehst du immer noch nicht? Sie können 0x und concat nicht verwenden, da Sie mit einem Fehler enden, wenn die Zeichenfolge leer ist. Wenn Sie eine einfache Alternative zu Ihrer Anfrage wünschen, versuchen Sie dieseSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy
499

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

Sicherheitswarnung : Diese Antwort entspricht nicht den bewährten Sicherheitsmethoden. Das Escaping ist nicht ausreichend, um eine SQL-Injection zu verhindern . Verwenden Sie stattdessen vorbereitete Anweisungen . Verwenden Sie die unten beschriebene Strategie auf eigenes Risiko. ( mysql_real_escape_string()Wurde auch in PHP 7 entfernt.)

WICHTIG

Der beste Weg, um SQL Injection zu verhindern, besteht darin, vorbereitete Anweisungen zu verwenden, anstatt zu maskieren , wie die akzeptierte Antwort zeigt.

Es gibt Bibliotheken wie Aura.Sql und EasyDB , mit denen Entwickler vorbereitete Anweisungen einfacher verwenden können. Weitere Informationen darüber, warum vorbereitete Anweisungen die SQL-Injection besser stoppen können , finden Sie in diesem mysql_real_escape_string()Bypass und den kürzlich 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 voll einer Funktion zu verwenden mysql_real_escape_string.

mysql_real_escape_stringNimmt eine Zeichenfolge, die in einer MySQL-Abfrage verwendet werden soll, und gibt dieselbe Zeichenfolge zurück, wobei alle SQL-Injection-Versuche sicher entkommen sind. Grundsätzlich werden die problematischen Anführungszeichen ('), die ein Benutzer möglicherweise eingibt, durch ein MySQL-sicheres Ersatzzeichen ersetzt, ein maskiertes Anführungszeichen \'.

HINWEIS: Sie müssen mit der Datenbank verbunden sein, um diese Funktion nutzen 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;

Weitere Informationen finden Sie unter MySQL - SQL Injection Prevention .

rahularyansharma
quelle
30
Dies ist das Beste, was Sie mit der alten MySQL-Erweiterung tun können. Für neuen Code wird empfohlen, zu mysqli oder PDO zu wechseln.
Álvaro González
7
Ich bin mit dieser "speziell entwickelten Funktion zur Verhinderung dieser Angriffe" nicht einverstanden. Ich denke, dieser mysql_real_escape_stringZweck besteht darin, die korrekte SQL-Abfrage für jede Eingabedatenzeichenfolge zu erstellen. Prävention SQL-Injektion ist der Nebeneffekt dieser Funktion.
Sekte
4
Sie verwenden keine Funktionen, um korrekte Eingabedatenzeichenfolgen zu schreiben. Sie schreiben nur die richtigen, die nicht entkommen müssen oder bereits entkommen sind. mysql_real_escape_string () wurde möglicherweise für den von Ihnen genannten Zweck entwickelt, aber sein einziger Wert ist die Verhinderung der Injektion.
Nazca
17
WARNUNG! mysql_real_escape_string() ist nicht unfehlbar .
Eggyal
9
mysql_real_escape_stringist jetzt veraltet, daher ist es keine praktikable Option mehr. Es wird in Zukunft aus PHP entfernt. Es ist am besten, sich den Empfehlungen der PHP- oder MySQL-Leute zuzuwenden.
JWW
462

Sie könnten so etwas Grundlegendes tun:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Dies wird nicht jedes Problem lösen, aber es ist ein sehr gutes Sprungbrett. Ich habe offensichtliche Punkte wie die Überprüfung der Existenz, des Formats (Zahlen, Buchstaben usw.) der Variablen ausgelassen.

Kiran Maniya
quelle
28
Ich habe Ihr Beispiel ausprobiert und es funktioniert gut für mich. Könnten Sie klarstellen, "das wird nicht jedes Problem lösen"
Chinook
14
Wenn Sie den String nicht zitieren, ist er immer noch injizierbar. Nehmen wir $q = "SELECT col FROM tbl WHERE x = $safe_var";zum Beispiel. Die Einstellung $safe_varauf 1 UNION SELECT password FROM usersfunktioniert in diesem Fall aufgrund fehlender Anführungszeichen. Es ist auch möglich, Zeichenfolgen mit CONCATund in die Abfrage einzufügen CHR.
Polynom
4
@Polynom Völlig richtig, aber ich würde dies nur als falsche Verwendung ansehen. Solange Sie es richtig verwenden, wird es definitiv funktionieren.
glglgl
22
WARNUNG! mysql_real_escape_string() ist nicht unfehlbar .
Eggyal
8
mysql_real_escape_stringist jetzt veraltet, daher ist es keine praktikable Option mehr. Es wird in Zukunft aus PHP entfernt. Es ist am besten, sich den Empfehlungen der PHP- oder MySQL-Leute zuzuwenden.
JWW
380

Was auch immer Sie letztendlich verwenden, stellen Sie sicher, dass Sie überprüfen, ob Ihre Eingabe noch nicht durch magic_quoteseinen anderen gut gemeinten Müll beschädigt wurde, und führen Sie ihn gegebenenfalls durch stripslashesoder was auch immer, um ihn zu bereinigen.

Rob
quelle
11
Tatsächlich; Laufen mit eingeschalteten magic_quotes fördert nur schlechtes Üben. Manchmal können Sie die Umgebung jedoch nicht immer auf diese Ebene steuern - entweder haben Sie keinen Zugriff auf die Verwaltung des Servers oder Ihre Anwendung muss mit Anwendungen koexistieren, die (Schauder) von einer solchen Konfiguration abhängen. Aus diesen Gründen ist es gut, tragbare Anwendungen zu schreiben - obwohl der Aufwand offensichtlich verschwendet wird, wenn Sie die Bereitstellungsumgebung steuern, z. B. weil es sich um eine interne Anwendung handelt oder nur in Ihrer spezifischen Umgebung verwendet wird.
Rob
24
Ab PHP 5.4 wurde der als "magische Zitate" bekannte Gräuel tot getötet . Und gute Befreiung von schlechtem Müll.
BryanH
363

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

Sicherheitswarnung : Diese Antwort entspricht nicht den bewährten Sicherheitsmethoden. Das Escaping ist nicht ausreichend, um eine SQL-Injection zu verhindern . Verwenden Sie stattdessen vorbereitete Anweisungen . Verwenden Sie die unten beschriebene Strategie auf eigenes Risiko. ( mysql_real_escape_string()Wurde auch in PHP 7 entfernt.)

Parametrisierte Abfrage- UND Eingabevalidierung ist der richtige Weg. Es gibt viele Szenarien, in denen SQL-Injection auftreten kann, obwohl mysql_real_escape_string()dies 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 'die Kapselung nicht schützen.

Quelle : Die unerwartete SQL-Injektion (wenn das Entkommen nicht ausreicht)

Cedric
quelle
2
Sie können die SQL-Injection verhindern, wenn Sie eine Eingabevalidierungstechnik anwenden, bei der Benutzereingaben anhand einer Reihe definierter Regeln für Länge, Typ und Syntax sowie anhand von Geschäftsregeln authentifiziert werden.
Josip Ivic
312

Meiner Meinung nach ist der beste Weg, um SQL-Injection in Ihrer PHP-Anwendung (oder einer anderen Webanwendung) generell zu verhindern, über die Architektur Ihrer Anwendung nachzudenken. Wenn der einzige Weg zum Schutz vor SQL-Injection darin besteht, eine spezielle Methode oder Funktion zu verwenden, die jedes Mal, wenn Sie mit der Datenbank sprechen, das Richtige tut, machen Sie es falsch. Auf diese Weise ist es nur eine Frage der Zeit, bis Sie vergessen, Ihre Abfrage irgendwann in Ihrem Code korrekt zu formatieren.

Die Übernahme des MVC-Musters und eines Frameworks wie CakePHP oder CodeIgniter ist wahrscheinlich der richtige Weg: In solchen Frameworks wurden allgemeine Aufgaben wie das Erstellen sicherer Datenbankabfragen gelöst und zentral implementiert. Sie helfen Ihnen dabei, Ihre Webanwendung sinnvoll zu organisieren, und lassen Sie mehr über das Laden und Speichern von Objekten nachdenken als über das sichere Erstellen einzelner SQL-Abfragen.

Johannes Fahrenkrug
quelle
5
Ich denke, Ihr erster Absatz ist wichtig. Verständnis ist der Schlüssel. Außerdem arbeitet nicht jeder für ein Unternehmen. Für eine große Anzahl von Menschen widersprechen Frameworks tatsächlich der Idee des Verstehens . Sich mit den Grundlagen vertraut zu machen, wird vielleicht nicht geschätzt, wenn man unter einer bestimmten Frist arbeitet, aber die Heimwerker da draußen genießen es, sich die Hände schmutzig zu machen. Framework-Entwickler sind nicht so privilegiert, dass sich alle anderen verbeugen und davon ausgehen müssen, dass sie niemals Fehler machen. Die Entscheidungsbefugnis ist nach wie vor wichtig. Wer soll sagen, dass mein Framework in Zukunft kein anderes Schema verdrängen wird?
Anthony Rutledge
@AnthonyRutledge Sie sind absolut korrekt. Es ist sehr wichtig zu verstehen, was los ist und warum. Die Wahrscheinlichkeit, dass ein bewährtes und aktiv genutztes und entwickeltes Framework auf viele Probleme gestoßen und diese gelöst und viele Sicherheitslücken behoben hat, ist jedoch ziemlich hoch. Es ist eine gute Idee, sich die Quelle anzusehen, um ein Gefühl für die Codequalität zu bekommen. Wenn es ein ungetestetes Durcheinander ist, ist es wahrscheinlich nicht sicher.
Johannes Fahrenkrug
3
Hier. Hier. Gute Argumente. Würden Sie jedoch zustimmen, dass viele Menschen ein MVC-System studieren und lernen können, es zu übernehmen, aber nicht jeder kann es von Hand reproduzieren (Controller und Server). Mit diesem Punkt kann man zu weit gehen. Muss ich meine Mikrowelle verstehen, bevor ich meine Erdnussbutter-Pekannuss-Kekse erhitze, die meine Freundin mir gemacht hat? ;-)
Anthony Rutledge
3
@AnthonyRutledge Ich stimme zu! Ich denke, der Anwendungsfall macht auch einen Unterschied: Baue ich eine Fotogalerie für meine persönliche Homepage oder erstelle ich eine Online-Banking-Webanwendung? Im letzteren Fall ist es sehr wichtig, die Details der Sicherheit zu verstehen und zu verstehen, wie ein von mir verwendetes Framework diese angeht.
Johannes Fahrenkrug
3
Ah, die Sicherheitsausnahme für die Do-it-yourself-Folge. Sehen Sie, ich neige dazu, alles zu riskieren und pleite zu gehen. :-) Scherz. Mit genügend Zeit können die Leute lernen, eine verdammt sichere Anwendung zu erstellen. Zu viele Menschen sind in Eile. Sie werfen die Hände hoch und gehen davon aus, dass die Frameworks sicherer sind . Schließlich haben sie nicht genug Zeit, um Dinge zu testen und herauszufinden. Darüber hinaus ist Sicherheit ein Bereich, der spezielle Studien erfordert. Es ist nicht etwas, was bloße Programmierer durch das Verständnis von Algorithmen und Entwurfsmustern genau wissen.
Anthony Rutledge
298

Ich bevorzuge gespeicherte Prozeduren ( MySQL unterstützt seit 5.0 gespeicherte Prozeduren ) aus Sicherheitsgründen - die Vorteile sind:

  1. In den meisten Datenbanken (einschließlich MySQL ) kann der Benutzerzugriff auf die Ausführung gespeicherter Prozeduren beschränkt werden. Die fein abgestimmte Sicherheitszugriffskontrolle ist nützlich, um eine Eskalation von Berechtigungsangriffen zu verhindern. Dies verhindert, dass gefährdete Anwendungen SQL direkt für die Datenbank ausführen können.
  2. Sie abstrahieren die SQL-Rohabfrage von der Anwendung, sodass der Anwendung weniger Informationen über die Datenbankstruktur zur Verfügung stehen. Dies erschwert es den Menschen, die zugrunde liegende Struktur der Datenbank zu verstehen und geeignete Angriffe zu entwerfen.
  3. Sie akzeptieren nur Parameter, daher sind die Vorteile parametrisierter Abfragen vorhanden. Natürlich - IMO müssen Sie Ihre Eingabe noch bereinigen - insbesondere, wenn Sie dynamisches SQL in der gespeicherten Prozedur verwenden.

Die Nachteile sind -

  1. Sie (gespeicherte Prozeduren) sind schwer zu warten und vermehren sich sehr schnell. Dies macht die Verwaltung zu einem Problem.
  2. Sie eignen sich nicht sehr gut für dynamische Abfragen. Wenn sie so aufgebaut sind, dass sie dynamischen Code als Parameter akzeptieren, werden viele Vorteile zunichte gemacht.
Nikhil
quelle
297

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. Ich möchte Ihnen jedoch eine gute Verhinderung von Links durch SQL-Injection vorschlagen.

Was ist SQL Injection und wie kann dies verhindert werden?

PHP-Handbuch für SQL-Injection

Microsoft-Erklärung zur SQL-Injektion und -Prävention in PHP

Und einige andere wie das Verhindern der SQL-Injektion mit MySQL und PHP .

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

Ich möchte Sie wissen lassen: Warum versuchen wir anhand eines kurzen Beispiels unten, die SQL-Injection zu verhindern:

Abfrage für Übereinstimmung der Anmeldungsauthentifizierung:

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

Nun, wenn jemand (ein Hacker) setzt

$_POST['email']= admin@emali.com' 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. Also, was wird passieren? Ein nicht autorisierter Benutzer (Hacker) kann sich als Administrator anmelden, ohne sein Kennwort zu haben. Jetzt kann er / sie alles tun, was der Administrator / die E-Mail-Person tun kann. Es ist sehr gefährlich, wenn die SQL-Injection nicht verhindert wird.

Manish Shrivastava
quelle
267

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

  1. Denken Sie an das Erlernen von PDO (PHP Data Objects) - es handelt sich um eine Datenbankzugriffsschicht, die eine einheitliche Methode für den Zugriff auf mehrere Datenbanken bietet.
  2. Denken Sie daran, MySQLi zu lernen
  3. Verwenden Sie native PHP-Funktionen wie: strip_tags , mysql_real_escape_string oder einfach , wenn die Variable numerisch ist (int)$foo. Lesen Sie mehr über Art von Variablen in PHP hier . Wenn Sie Bibliotheken wie PDO oder MySQLi verwenden, verwenden Sie immer PDO :: quote () und mysqli_real_escape_string () .

Beispiele für Bibliotheken:

---- 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 :

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 die API gewöhnen. Aus Sicherheitsgründen sind beide sicher, solange der Entwickler sie so verwendet, wie sie verwendet werden sollen

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

Und bitte testen Sie jede Abfrage in Ihrer Datenbank - dies ist ein besserer Weg, um eine Injektion zu verhindern.

RDK
quelle
dass mysqli falsch ist. Der erste Parameter drückt die Datentypen aus.
mickmackusa
257

Wenn möglich, geben Sie die Typen Ihrer Parameter ein. Aber es funktioniert 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 . "')");
devOp
quelle
3
Dies ist einer der wenigen Fälle, in denen ich anstelle einer vorbereiteten Anweisung einen "Escape-Wert" verwenden würde. Die Ganzzahlkonvertierung ist äußerst effizient.
HoldOffHunger
233

Wenn Sie Cache-Engines wie Redis oder Memcached nutzen möchten, ist DALMP möglicherweise die richtige Wahl. Es verwendet reines MySQLi . Überprüfen Sie Folgendes : DALMP Database Abstraction Layer für MySQL mit PHP.

Sie können Ihre Argumente auch vorbereiten, bevor Sie Ihre Abfrage vorbereiten, sodass Sie dynamische Abfragen erstellen und am Ende eine vollständig vorbereitete Anweisungsabfrage haben können. DALMP Database Abstraction Layer für MySQL mit PHP.

Peter Mortensen
quelle
224

Für diejenigen, die sich nicht sicher sind, wie sie PDO verwenden sollen (aus den mysql_Funktionen), habe ich einen sehr, sehr einfachen PDO-Wrapper erstellt , der eine einzelne Datei ist. Es zeigt, wie einfach es ist, alle gängigen Aufgaben zu erledigen, die Anwendungen ausführen müssen. Funktioniert mit PostgreSQL, MySQL und SQLite.

Im Grunde genommen, lesen Sie es , während Sie das Handbuch zu lesen , um zu sehen , wie die PDO - Funktionen im realen Leben Gebrauch zu setzen , um es einfach zu speichern und abzurufen Werte im Format Sie mögen.

Ich möchte eine einzelne Spalte

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

Ich möchte ein Array (Schlüssel => Wert) Ergebnisse (dh zum Erstellen einer Auswahlbox)

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

Ich möchte ein Ergebnis für eine einzelne Zeile

$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));
Xeoncross
quelle
222

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

Zum Beispiel:

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

mysql_escape_string - Entgeht eine Zeichenfolge 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 bekommen Sie:

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

Einige Richtlinien zum Escapezeichen von Sonderzeichen in SQL-Anweisungen.

Verwenden Sie MySQL nicht . Diese Erweiterung ist veraltet. Verwenden Sie stattdessen MySQLi oder PDO .

MySQLi

Zum manuellen Escapezeichen von Sonderzeichen in einer Zeichenfolge können Sie die Funktion mysqli_real_escape_string verwenden. Die Funktion funktioniert nur dann ordnungsgemäß, wenn der richtige Zeichensatz mit mysqli_set_charset festgelegt wurde .

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 Sie zum automatischen Escapezeichen von Werten mit vorbereiteten Anweisungen mysqli_prepare und mysqli_stmt_bind_param, wobei Typen für die entsprechenden Bindevariablen für eine geeignete Konvertierung angegeben werden müssen:

Beispiel:

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

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

$stmt->execute();

Unabhängig davon, ob Sie vorbereitete Anweisungen verwenden oder mysqli_real_escape_string, müssen Sie immer die Art der Eingabedaten kennen, mit denen Sie arbeiten.

Wenn Sie also eine vorbereitete Anweisung verwenden, müssen Sie die Typen der Variablen für die mysqli_stmt_bind_paramFunktion angeben .

Und die Verwendung von mysqli_real_escape_stringdient dazu, wie der Name schon sagt, Sonderzeichen in einer Zeichenfolge zu maskieren, sodass Ganzzahlen nicht sicher sind. Mit dieser Funktion soll verhindert werden, dass die Zeichenfolgen in SQL-Anweisungen unterbrochen werden und die Datenbank dadurch beschädigt wird. mysqli_real_escape_stringist eine nützliche Funktion bei richtiger Verwendung, insbesondere in Kombination mit sprintf.

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
Danijel
quelle
3
Die Frage ist sehr allgemein. Einige gute Antworten oben, aber die meisten schlagen vorbereitete Aussagen vor. MySQLi async unterstützt keine vorbereiteten Anweisungen, daher scheint der Sprintf eine großartige Option für diese Situation zu sein.
Dustin Graham
183

Die einfache Alternative zu diesem Problem könnte gelöst werden, indem entsprechende Berechtigungen in der Datenbank selbst erteilt werden. Beispiel: Wenn Sie eine MySQL-Datenbank verwenden, geben Sie die Datenbank über das Terminal oder die bereitgestellte Benutzeroberfläche ein und folgen Sie einfach diesem Befehl:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Dadurch wird der Benutzer darauf beschränkt, sich nur auf die angegebenen Abfragen zu beschränken. Entfernen Sie die Löschberechtigung, damit die Daten niemals aus der von der PHP-Seite ausgelösten Abfrage gelöscht werden. Als zweites müssen Sie die Berechtigungen leeren, damit MySQL die Berechtigungen und Aktualisierungen aktualisiert.

FLUSH PRIVILEGES; 

Weitere Informationen zu Flush .

Um die aktuellen Berechtigungen für den Benutzer anzuzeigen, wird die folgende Abfrage ausgelöst.

select * from mysql.user where User='username';

Erfahren Sie mehr über GRANT .

Apurv Nerlekar
quelle
25
Diese Antwort ist im Wesentlichen falsch , da sie nicht dazu beiträgt, eine Injektionsprävention zu verhindern, sondern nur versucht, die Konsequenzen abzuschwächen. Vergeblich.
Ihr gesunder Menschenverstand
1
Richtig, es bietet keine Lösung, aber Sie können dies vorab tun, um Dinge zu vermeiden.
Apurv Nerlekar
1
@Apurv Wenn mein Ziel darin besteht, private Informationen aus Ihrer Datenbank zu lesen, bedeutet es nichts, nicht über die Berechtigung DELETE zu verfügen.
Alex Holsgrove
1
@AlexHolsgrove: Machen Sie es sich gemütlich, ich habe nur bewährte Methoden vorgeschlagen, um die Konsequenzen abzuschwächen.
Apurv Nerlekar
2
@Apurv Du willst die Konsequenzen nicht "mildern", du willst alles tun, um dich davor zu schützen. Um fair zu sein, ist es wichtig, den richtigen Benutzerzugriff festzulegen, aber nicht wirklich das, was das OP verlangt.
Alex Holsgrove
177

In Bezug auf viele nützliche Antworten hoffe ich, diesem Thread einen Mehrwert zu verleihen.

SQL Injection ist ein Angriff, der über Benutzereingaben erfolgen kann (Eingaben, die von einem Benutzer ausgefüllt und dann in Abfragen verwendet werden). Die SQL-Injection-Muster sind die korrekte Abfragesyntax, obwohl wir sie als schlechte Abfragen aus schlechten Gründen bezeichnen können, und wir gehen davon aus, dass es möglicherweise eine schlechte Person gibt, die versucht, geheime Informationen (unter Umgehung der Zugriffskontrolle) abzurufen, die die drei Sicherheitsprinzipien (Vertraulichkeit) beeinflussen , Integrität und Verfügbarkeit).

Jetzt geht es darum, Sicherheitsbedrohungen wie SQL-Injection-Angriffe zu verhindern, die Frage (wie man einen SQL-Injection-Angriff mit PHP verhindert) realistischer zu gestalten, Daten zu filtern oder Eingabedaten zu löschen, wenn Benutzereingabedaten im Inneren verwendet werden Eine solche Abfrage mit PHP oder einer anderen Programmiersprache ist nicht der Fall. Wie von mehr Personen empfohlen, moderne Technologien wie vorbereitete Anweisungen oder andere Tools zu verwenden, die derzeit die SQL-Injection-Prävention unterstützen, sind diese Tools nicht mehr verfügbar? Wie sichern Sie Ihre Bewerbung?

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

Datenfilterung für (Konvertieren unsicherer Daten in sichere Daten)

Beachten Sie, dass PDO und MySQLi nicht verfügbar sind. Wie können Sie Ihre Bewerbung sichern? Zwingst du mich, sie zu benutzen? Was ist mit anderen Sprachen als PHP? Ich ziehe es vor, allgemeine Ideen zu liefern, da diese für eine breitere Grenze verwendet werden können, nicht nur für eine bestimmte Sprache.

  1. SQL-Benutzer (Einschränkung der Benutzerberechtigung): Die häufigsten SQL-Vorgänge sind (SELECT, UPDATE, INSERT). Warum sollte die UPDATE-Berechtigung dann einem Benutzer erteilt werden, der sie nicht benötigt? Zum Beispiel, Login und Suchseiten verwenden nur SELECT, dann, warum die Verwendung DB Benutzer auf diesen Seiten mit hohen Privilegien?

REGEL: 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 Abfragebenutzereingabe erstellt wird, sollte diese validiert und gefiltert werden. Für Programmierer ist es wichtig, einige Eigenschaften für jede Benutzereingabevariable zu definieren: Datentyp, Datenmuster und Datenlänge . Ein Feld, das eine Zahl zwischen (x und y) ist, muss anhand der genauen Regel genau validiert werden. Für ein Feld, das eine Zeichenfolge (Text) ist: Muster ist der Fall. Beispielsweise darf ein Benutzername nur einige Zeichen enthalten sagen Sie [a-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 sind für mich Best Practices.

  2. Verwenden Sie andere Tools: Hier werde ich Ihnen auch zustimmen, dass eine vorbereitete Anweisung (parametrisierte Abfrage) und gespeicherte Prozeduren. Die Nachteile hierbei sind, dass diese Methoden fortgeschrittene Fähigkeiten erfordern, die für die meisten Benutzer nicht vorhanden sind. Die Grundidee besteht darin, zwischen der SQL-Abfrage und den darin verwendeten Daten zu unterscheiden. Beide Ansätze können auch bei unsicheren Daten verwendet werden, da die hier eingegebenen Benutzereingaben der ursprünglichen Abfrage nichts hinzufügen, z. B. (any oder x = x).

Weitere Informationen finden Sie im OWASP SQL Injection Prevention Cheat Sheet .

Wenn Sie ein fortgeschrittener Benutzer sind, können Sie diese Verteidigung nach Belieben verwenden. Für Anfänger ist es jedoch besser, Eingabedaten so weit wie möglich zu filtern, wenn sie eine gespeicherte Prozedur nicht schnell implementieren und die Anweisung vorbereiten können.

Nehmen wir zum Schluss an, dass ein Benutzer diesen Text unten 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 Anweisung und gespeicherte Prozeduren überprüft werden. Um jedoch auf der sicheren Seite zu sein, beginnt die Verwendung nach der Filterung und Validierung der Benutzerdaten.

Der letzte Punkt ist das Erkennen 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 und root. Sobald diese Wörter erkannt wurden, können Sie die Eingabe vermeiden.

UPDATE 1:

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

Primäre Verteidigung:

Option 1: Verwendung vorbereiteter Anweisungen (parametrisierte Abfragen)
Option 2: Verwendung gespeicherter Prozeduren
Option 3: Entkommen aller vom Benutzer bereitgestellten Eingaben

Zusätzliche Verteidigung:

Auch erzwingen: Geringste Berechtigung
Führen Sie auch eine Überprüfung der Eingabe auf der weißen Liste durch

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

Update 2:

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

Escaping und SQL-Injection

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

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

Update 3:

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

GU:

$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 Aussage erwähnt,

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

Dies beweist daher, dass eine Datenüberprüfung, wie sie beispielsweise intval()für ganzzahlige Werte geeignet ist, vor dem Senden einer Abfrage eine gute Idee ist. Darüber hinaus ist das Verhindern böswilliger Benutzerdaten vor dem Senden der Abfrage ein korrekter und gültiger Ansatz .

Weitere Informationen finden Sie in dieser Frage: PDO sendet eine Rohabfrage an MySQL, während Mysqli eine vorbereitete Abfrage sendet. Beide führen zum gleichen Ergebnis

Verweise:

  1. SQL Injection Cheat Sheet
  2. SQL-Injektion
  3. Informationssicherheit
  4. Sicherheitsgrundsätze
  5. Datenvalidierung
Benutzer1646111
quelle
175

Sicherheitswarnung : Diese Antwort entspricht nicht den bewährten Sicherheitsmethoden. Das Escaping ist nicht ausreichend, um eine SQL-Injection zu verhindern . Verwenden Sie stattdessen vorbereitete Anweisungen . Verwenden Sie die unten beschriebene Strategie auf eigenes Risiko. ( mysql_real_escape_string()Wurde auch in PHP 7 entfernt.)

Veraltete Warnung : Die MySQL-Erweiterung ist derzeit veraltet. Wir empfehlen die Verwendung der PDO-Erweiterung

Ich verwende drei verschiedene Methoden, um zu verhindern, dass meine Webanwendung für SQL-Injection anfällig ist.

  1. Verwendung von mysql_real_escape_string(), die eine vordefinierte Funktion ist PHP , und dieser Code add umgekehrten Schrägstrichen auf die folgenden Figuren: \x00, \n, \r, \, ', "und \x1a. Übergeben Sie die Eingabewerte als Parameter, um die Wahrscheinlichkeit einer SQL-Injection zu minimieren.
  2. Am weitesten fortgeschritten ist die Verwendung von gU.

Ich hoffe, dies wird dir helfen.

Betrachten Sie die folgende Abfrage:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string () schützt hier nicht. Wenn Sie in Ihrer Abfrage einfache Anführungszeichen ('') für Ihre Variablen verwenden, schützt dies Sie davor. Hier ist eine Lösung dafür:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

Diese Frage hat einige gute Antworten darauf.

Ich schlage vor, PDO ist die beste Option.

Bearbeiten:

mysql_real_escape_string()ist ab PHP 5.5.0 veraltet. Verwenden Sie entweder mysqli oder PDO.

Eine Alternative zu mysql_real_escape_string () ist

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Beispiel:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");
Soumalya Banerjee
quelle
169

Eine einfache Möglichkeit wäre die Verwendung eines PHP-Frameworks wie CodeIgniter oder Laravel, das über integrierte Funktionen wie Filterung und aktive Aufzeichnung verfügt, sodass Sie sich nicht um diese Nuancen kümmern müssen.

Deepak Thomas
quelle
7
Ich denke, der springende Punkt der Frage ist, dies ohne Verwendung eines solchen Rahmens zu erreichen.
Sanke
147

Warnung: Der in dieser Antwort beschriebene Ansatz gilt nur für sehr spezifische Szenarien und ist nicht sicher, da SQL-Injection-Angriffe nicht nur auf der Injection-Fähigkeit beruhen 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 abfangen, 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 darauf achten, dass Sie keine sichere Abfrage von Ihrer Site aus neu schreiben. Der obige Code gibt Ihnen einen Tipp, wie Sie diese hacking-spezifische dynamische Abfragezeichenfolge in eine Seite umschreiben oder umleiten können (dies hängt von Ihnen ab) , auf der die IP-Adresse des Angreifers oder AUCH IHRE COOKIES, der Verlauf, der Browser oder andere vertrauliche Informationen gespeichert werden Informationen, damit Sie später mit ihnen umgehen können, indem Sie ihr Konto sperren oder die Behörden kontaktieren.

5ervant
quelle
Was ist 1-1=0los? :)
Rápli András
@ RápliAndrás Irgendeine Art von ([0-9\-]+)=([0-9]+).
5ervant
127

Es gibt so viele Antworten für PHP und MySQL , aber hier ist Code für PHP und Oracle, um die SQL-Injection sowie die regelmäßige Verwendung von oci8-Treibern zu verhindern:

$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);
Chintan Gor
quelle
Bitte erläutern Sie die Parameter von oci_bind_by_name.
Jahanzeb Awan
127

Eine gute Idee ist es, einen objektrelationalen Mapper wie Idiorm zu verwenden :

$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! Es werden auch Modellkollektionen mit Methodenverkettung unterstützt, um Aktionen auf mehrere Ergebnisse gleichzeitig und mehrere Verbindungen zu filtern oder anzuwenden.

Thomas Ahle
quelle
124

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

Sicherheitswarnung : Diese Antwort entspricht nicht den bewährten Sicherheitsmethoden. Das Escaping ist nicht ausreichend, um eine SQL-Injection zu verhindern . Verwenden Sie stattdessen vorbereitete Anweisungen . Verwenden Sie die unten beschriebene Strategie auf eigenes Risiko. ( mysql_real_escape_string()Wurde auch in PHP 7 entfernt.)

Die Verwendung von PDO und MYSQLi ist eine gute Methode , um SQL-Injektionen zu verhindern. Wenn Sie jedoch wirklich mit MySQL-Funktionen und -Abfragen arbeiten möchten, ist die Verwendung besser

mysql_real_escape_string

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

Es gibt mehr Möglichkeiten, dies zu verhindern: wie Identifizieren - Wenn die Eingabe eine Zeichenfolge, eine Zahl, ein Zeichen oder ein Array ist, gibt es so viele integrierte Funktionen, um dies zu erkennen. Es ist auch besser, diese Funktionen zum Überprüfen der Eingabedaten 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 zu verwenden, um Eingabedaten zu überprüfen mysql_real_escape_string.

Rakesh Sharma
quelle
10
Es macht auch absolut keinen Sinn, $ _POST-Array-Mitglieder mit is_string () zu überprüfen
Your Common Sense
21
WARNUNG! mysql_real_escape_string() ist nicht unfehlbar .
Eggyal
10
mysql_real_escape_stringist jetzt veraltet, daher ist es keine praktikable Option mehr. Es wird in Zukunft aus PHP entfernt. Es ist am besten, sich den Empfehlungen der PHP- oder MySQL-Leute zuzuwenden.
JWW
2
Thema: Vertrauen Sie den vom Benutzer übermittelten Daten nicht. Alles, was Sie erwarten, sind Mülldaten mit Sonderzeichen oder boolescher Logik, die selbst Teil der SQL-Abfrage werden sollten, die Sie möglicherweise ausführen. Behalten Sie $ _POST-Werte nur als Daten bei, nicht als SQL-Teil.
Bimal Poudel
88

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 entweicht unter Berücksichtigung des Variablentyps. Wenn Sie versuchen, Tabellen- und Spaltennamen zu parametrisieren, schlägt dies fehl, da jede Zeichenfolge in Anführungszeichen gesetzt wird, was eine ungültige Syntax darstellt.

SICHERHEITS-UPDATE: In der vorherigen str_replaceVersion wurden Injektionen durch Hinzufügen von {#} Token zu Benutzerdaten zugelassen. Diese preg_replace_callbackVersion verursacht keine Probleme, wenn der Ersatz diese Token enthält.

Calmarius
quelle