Frage

Lassen Sie uns sagen, ich habe Code wie folgt:

$dbh = new PDO("blahblah");

$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

Die PDO-Dokumentation sagt:

  

Die Parameter für vorbereitete Anweisungen müssen nicht zitiert werden; der Fahrer übernimmt das für Sie.

Ist das wirklich alles, was ich tun müssen, um SQL-Injektionen zu vermeiden? Ist es wirklich so einfach?

Sie können MySQL übernehmen, wenn es einen Unterschied macht. Außerdem ist mir wirklich nur neugierig über die Verwendung von Prepared Statements gegen SQL-Injection. In diesem Zusammenhang ist es mir nicht um XSS oder andere mögliche Schwachstellen.

War es hilfreich?

Lösung

Die kurze Antwort ist NO , bereitet PDO werden Sie von allen möglichen SQL-Injection-Angriffe nicht verteidigen. Für bestimmte obskuren Rand Fällen.

Ich Anpassung diese Antwort über PDO ...

sprechen

Die lange Antwort ist nicht so einfach. Es basiert einen Angriff hier demonstriert rel="noreferrer">.

Der Angriff

Also, lassen Sie uns beginnen, durch den Angriff zeigt ...

$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));

Unter bestimmten Umständen, dass mehr als 1 Zeile zurück. Lassen Sie uns sezieren, was hier vor sich geht:

  1. Die Auswahl eines Zeichensatz

    $pdo->query('SET NAMES gbk');
    

    Für diesen Angriff zu arbeiten, müssen wir die Codierung, die der Server auf der Verbindung erwarten ist sowohl ' wie in ASCII zu codieren, dh 0x27 und einige Zeichen letztes Byte, das haben, ist ein ASCII-\ dh 0x5c . Wie sich herausstellt, gibt es 5 solche Codierungen in MySQL unterstützt 5.6 standardmäßig: big5, cp932, gb2312, gbk und sjis. Wir wählen gbk hier.

    Nun, es ist sehr wichtig, die Verwendung von SET NAMES hier zu beachten. Dies stellt den Zeichensatz AUF DEM SERVER . Es gibt einen anderen Weg, es zu tun, aber wir werden es bald genug bekommen.

  2. Die Payload

    Die Nutzlast wir für diese Injektion verwenden werden beginnt mit der Byte-Reihenfolge 0xbf27. In gbk, das ist ein ungültiges Multibyte-Zeichen; in latin1, dann ist es die Zeichenfolge ¿'. Beachten Sie, dass in latin1 und gbk, 0x27 auf seinem eigenen ist ein wörtlicher ' Charakter.

    Wir haben diese Nutzlast gewählt, weil, wenn wir addslashes() sie aufgefordert haben, würden wir eine ASCII-\ heißt 0x5c, vor dem ' Zeichen einzufügen. So würden wir mit 0xbf5c27 aufzuwickeln, die in gbk ist eine zwei Zeichensequenz: 0xbf5c von 0x27 gefolgt. Oder in anderen Worten, ein gültig Charakter von einer unescaped ' gefolgt. Aber wir verwenden addslashes() nicht. Also auf zum nächsten Schritt ...

  3. $ stmt-> execute ()

    Wichtig ist hier zu erkennen ist, dass PDO standardmäßig tut nicht zu tun wahr vorbereitete Anweisungen. Es emuliert sie (für MySQL). Daher intern PDO baut den Query-String, ruft mysql_real_escape_string() (die MySQL-C-API-Funktion) an jedem gebundenen String-Wert.

    Die C-API-Aufruf zu mysql_real_escape_string() unterscheidet sich von addslashes(), dass er die Verbindung Zeichensatz kennt. So kann er die Flucht richtig für den Zeichensatz führt, dass der Server erwartet. Doch bis zu diesem Punkt, denkt der Kunde, dass wir nach wie vor mit latin1 für die Verbindung sind, weil wir es nie anders gesagt. Wir haben sagen die Server wir gbk verwenden, aber die Client immer noch denkt, es ist latin1.

    Daher der Aufruf an mysql_real_escape_string() fügt den umgekehrten Schrägstrich, und wir haben einen frei hängenden ' Charakter in unserem „entkommen“ Inhalt! In der Tat, wenn wir bei $var im gbk Zeichensatz sehen waren, würden wir sehen:

    縗' OR 1=1 /*

    Was genau ist es, was der Angriff erfordert.

  4. Die Abfrage

    Dieser Teil ist nur eine Formalität, aber hier ist die gerenderte Abfrage:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Herzlichen Glückwunsch, Sie gerade angegriffen erfolgreich ein Programm mit PDO Prepared Statements ...

The Simple Fix

Jetzt ist es erwähnenswert, dass Sie dies durch die Deaktivierung emuliert verhindern können vorbereitet stNG:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Dies wird in der Regel in einer echten vorbereiteten Aussage führen (das heißt, die Daten in einem separaten Paket, das von der Abfrage geschickt werden). Beachten Sie jedoch, dass PDO wird still Rückfall Anweisungen emuliert, dass MySQL nicht vorbereitet nativ: diejenigen, die es ist, kann aufgelistet im Handbuch, aber passen Sie die entsprechende Server-Version wählen).

Die richtige Fix

Das Problem hier ist, dass wir den C-API mysql_set_charset() statt SET NAMES nicht angerufen haben. Wenn wir das täten, würden wir werden fein versehen wir seit 2006 eine MySQL-Release verwenden.

Wenn Sie eine frühere MySQL-Release verwenden, dann ein Fehler mysql_real_escape_string() bedeutete, dass ungültige Mehrbytezeichen wie die in unserer Nutzlast als einzelnes Bytes behandelt wurden, für die Flucht Zwecke auch wenn der Client korrekt der Verbindung Codierung informiert worden und so würde dieser Angriff noch gelingen. Der Fehler wurde behoben in MySQL 4.1.20 5.0.22 und 5.1.11 .

Aber das Schlimmste ist, dass PDO nicht den C-API für mysql_set_charset() bis 5.3.6, so in früheren Versionen es aussetzen kann nicht verhindern, diesen Angriff für jeden möglichen Befehl!  Es wird nun als DSN-Parameter ausgesetzt, die sein sollte verwendet statt SET NAMES ...

Die Saving Grace

Wie wir am Anfang gesagt, für diesen Angriff der Datenbankverbindung zu arbeiten, muß mit einem gefährdeten Zeichensatz codiert werden. utf8mb4 nicht anfällig und noch unterstützen kann alle Unicode-Zeichen: so könnten Sie diese stattdessen aber zu verwenden, wählen sie seit MySQL 5.5.3 nur verfügbar gewesen. Eine Alternative ist, utf8 , die auch nicht verwundbar und kann die Gesamtheit des Unicode Basic Multilingual Plane unterstützen.

Alternativ können Sie ermöglichen die NO_BACKSLASH_ESCAPES SQL-Modus, die (unter anderem) verändert den Betrieb von mysql_real_escape_string(). Mit diesem Modus aktiviert ist, wird 0x27 mit 0x2727 ersetzt werden, anstatt 0x5c27 und damit die Flucht Prozess nicht erstellen gültige Zeichen in eines der gefährdeten Codierungen, wo sie vorher nicht vorhandenen (dh 0xbf27 noch 0xbf27 etc. ) -so den Server ablehnen noch die Zeichenfolge als ungültig. Allerdings sieht @ eggyal Antwort für eine andere Schwachstelle, die aus der Benutzung dieses SQL-Modus (wenn auch nicht mit PDO) entstehen kann.

Sicher Beispiele

Die folgenden Beispiele sind sicher:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Da der Server ist erwartet utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Da haben wir richtig eingestellt den Zeichensatz, damit der Client und der Server übereinstimmen.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Weil wir emulierte vorbereitete Anweisungen ausgeschaltet.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Weil wir die Zeichen gesetzt habenacter richtig eingestellt.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Da MySQLi tut wahr vorbereitete Anweisungen die ganze Zeit.

Verpackung Up

Wenn Sie:

  • Mit modernen Versionen von MySQL (Ende 5.1, alle 5.5, 5.6, usw.) und PDO des DSN charset Parametern (in PHP ≥ 5.3.6)

oder

  • Sie ein verwundbares Zeichen nicht für die Verbindung Codierung gesetzt verwenden
  • (nur utf8 / latin1 / ascii / etc verwenden)

oder

  • Aktivieren NO_BACKSLASH_ESCAPES SQL-Modus

Sie sind 100% sicher.

Ansonsten bist du verwundbar , obwohl Sie PDO Prepared Statements verwenden ...

Nachtrag

Ich habe an einem Patch langsam gearbeitet, um die Standardeinstellung ändern bereitet sich auf eine zukünftige Version von PHP nicht zu emulieren. Das Problem, das ich laufe in ist, dass eine Menge Tests brechen, wenn ich das tun. Ein Problem ist, dass emulierten bereitet nur Syntaxfehler werfen auf ausführen, aber wahr bereitet werden Fehler werfen auf vorbereiten. So dass Probleme verursachen (und ist Teil des Grundes, Tests Borking werden).

Andere Tipps

Prepared Statements / parametrisierte Abfragen sind in der Regel ausreichend, um zu verhindern 1. Ordnung Injektion auf dieser Aussage * . Wenn Sie un-geprüft dynamische SQL anderswo in Ihrer Anwendung verwenden, sind Sie immer noch anfällig für 2. Ordnung Injektion.

2. Ordnung Injektionsmittel Daten über die Datenbank einmal durchlaufen wurden, bevor in einer Abfrage enthalten ist, und sind viel schwieriger abziehen. AFAIK, Sie fast nie wirkliche entworfen 2. Ordnung Angriffe sehen, wie es in der Regel einfacher für Angreifer ist sozial Ingenieur ihren Weg in, aber man manchmal 2. Ordnung Bugs auftauchen, weil der zusätzliche gutartigen ' Zeichen oder ähnliches.

Sie können eine Injektionsangriff 2. Ordnung erreichen, wenn Sie einen Wert verursachen können in einer Datenbank gespeichert werden, die später als eine wörtliche in einer Abfrage verwendet wird. Als Beispiel sagen wir, Sie die folgenden Informationen als Ihren neuen Benutzernamen eingeben, wenn Sie ein Konto auf einer Website zu schaffen (vorausgesetzt, MySQL DB für diese Frage):

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

Wenn es keine andere Beschränkungen des Benutzername sind, eine vorbereitete Erklärung wäre immer noch sicher, dass die oben eingebettete Abfrage nicht zum Zeitpunkt des Einsatzes nicht ausführt, und den Wert korrekt in der Datenbank. vorstellen jedoch, dass später die Anwendung Ihren Benutzernamen aus der Datenbank abruft, und verwendet die String-Verkettung, um diesen Wert eine neue Abfrage enthalten. Man könnte jemand anderes, das Passwort zu sehen. Da die ersten paar Namen in der Tabelle Nutzer neigen dazu, Administratoren zu sein, können Sie auch verschenkt haben gerade den Hof. (Beachten Sie auch: ein Grund mehr ist nicht zum Speichern von Passwörtern im Klartext)

Wir sehen also, dass vorbereitete Anweisungen für eine einzelne Abfrage genug sind, aber selbst sie sind nicht ausreichend gegen SQL-Injection-Angriffe während einer gesamten Anwendung zu schützen, weil sie einen Mechanismus fehlt zu erzwingen dass alle Zugang zu einer Datenbank innerhalb der Anwendung verwendet sicheren Code. Jedoch im Rahmen eines ordnungsgemäßen Anwendung Design verwendet - die Praktiken wie Code-Review oder statische Analyse oder Verwendung eines ORM, Datenschicht oder Dienstschicht enthalten kann, die dynamische SQL begrenzt - Prepared Statements sind das primäre Werkzeug, um das SQL-Injection Problem zu lösen. Wenn Sie gutes Anwendung Design-Prinzipien, wie folgen, dass Ihr Datenzugriff von dem Rest Ihrer getrennt Programm, wird es einfach zu erzwingen oder Prüfung, dass jede Abfrage richtig Parametrierung verwendet. In diesem Fall SQL-Injektion (sowohl erste und zweite Ordnung) wird vollständig verhindert.


* Es stellt sich heraus, dass MySQL / PHP sind (okay, waren) nur stumm über Parameter die Handhabung beim breiten Zeichen beteiligt sind, und es ist immer noch ein selten Fall in der andere hoch gestimmt Antwort hier , dass die Injektion durch eine parametrisierte Abfrage rutschen lassen kann.

Nein, sie sind nicht immer.

Es hängt davon ab, ob Sie erlauben eine Benutzereingabe in der Abfrage selbst gestellt werden. Zum Beispiel:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

wäre anfällig für SQL-Injektionen und mit Prepared Statements in diesem Beispiel wird nicht funktionieren, weil die Benutzereingabe als Kennung verwendet wird, nicht als Daten. Die richtige Antwort wäre hier eine Art von Filterung zu verwenden / Validierung wie:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

Hinweis: Sie können nicht PDO verwenden, um Daten zu binden, die außerhalb von DDL (Data Definition Language) geht, das heißt das nicht funktioniert:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

Der Grund, warum die oben nicht der Fall funktioniert, weil DESC und ASC sind nicht Daten . PDO kann nur entweichen für Daten . Zweitens, können Sie nicht einmal ' Anführungszeichen um es ausdrückte. Die einzige Möglichkeit, Benutzer gewählte Sortierung zu erlauben, manuell filtern und überprüfen, dass es entweder DESC oder ASC.

Ja, es ist ausreichend. Die Art und Weise Injektion Attacken arbeiten, ist durch irgendwie eine Interpreter (die Datenbank) immer etwas zu bewerten, die Daten sein sollte, als ob es Code war. Dies ist nur möglich, wenn Sie in dem gleichen Medium Code und Daten mischen (z. B. wenn Sie eine Abfrage als String konstruieren).

Parametrisierte Abfragen arbeiten, indem sie den Code zu senden und die Daten getrennt, so würde es nie möglich sein, ein Loch in dem finden.

Sie können nach wie vor, obwohl andere Pritz-Angriffe anfällig sein. Zum Beispiel, wenn Sie die Daten in einer HTML-Seite verwenden, können Sie zu XSS-Attacken unterliegen.

Nein, das ist nicht genug (in bestimmten Fällen)! Standardmäßig verwendet PDO vorbereitete Anweisungen emuliert, wenn MySQL als Datenbanktreiber. Sie sollten immer vorbereitete Anweisungen emuliert deaktivieren, wenn die Verwendung von MySQL und PDO:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Eine andere Sache, die immer getan werden, sollte es die richtige Codierung der Datenbank festgelegt:

$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

Auch diese Frage im Zusammenhang sehen: Wie kann ich SQL verhindern Injektion in PHP?

Beachten Sie auch, dass nur über die Datenbank Seite der Dinge ist, würden Sie noch selbst zu beobachten, wenn die Anzeige von Daten. Z.B. von htmlspecialchars() wieder mit der richtigen Codierung und unter Angabe Stil verwendet wird.

Persönlich würde ich immer irgendeine Form von sanitären Einrichtungen auf den Daten läuft zunächst als man kann nie Vertrauen Benutzereingabe, jedoch bei der Verwendung von Platzhalter / Parameterbindung der eingegebenen Daten an den Server separat an die SQL-Anweisung gesendet werden und dann zusammen binded. Der Schlüssel hier ist, dass dies die zur Verfügung gestellten Daten auf einen bestimmten Typ bindet und eine besondere Verwendung und beseitigt jede Möglichkeit, die Logik der SQL-Anweisung zu ändern.

Eaven, wenn Sie SQL-Injection-Front-End verhindern werden, HTML oder js Kontrollen verwenden, würden Sie, dass die Front-End-Kontrollen "umgehbar" betrachten müssen.

Sie können js deaktivieren oder ein Muster mit einem Front-End-Entwicklungs-Tool (Baujahr mit Firefox oder Chrome heute) bearbeiten.

Also, um SQL-Injection zu verhindern, wäre rechtses Eingangsdatum Backend in Ihrem Controller zu sanieren.

Ich möchte Ihnen vorschlagen zu verwenden filter_input () nativen PHP-Funktion, um GET und INPUT-Werte zu sanieren.

Wenn Sie im Voraus mit Sicherheit gehen wollen, für eine sinnvolle Datenbankabfragen, ich möchte vorschlagen, Sie reguläre Ausdrücke zu verwenden, um Datenformat zu validieren. preg_match () werden Sie in diesem Fall helfen! Aber Achtung! Regex-Engine ist nicht so leicht. Verwenden Sie es nur, wenn nötig, sonst wird Ihre Anwendung Leistungen sinken.

Sicherheit hat ein Kosten, aber verschwenden Sie Ihre Leistung nicht!

Einfach Beispiel:

Wenn Sie überprüfen verdoppeln wollen, wenn ein Wert, von GET erhielt eine Zahl, weniger als 99     if (! preg_match ( '/ [0-9] {1,2} /')) {...} ist heavyer von

if (isset($value) && intval($value)) <99) {...}

Also, die endgültige Antwort lautet: „Nein PDO Prepared Statements nicht alle Arten von SQL-Injection verhindern!“; Dabei spielt es keine unerwarteten Werte vermeiden, nur unerwartete Verkettung

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top