Die kurze Antwort lautet NO Die PDO-Vorbereitungen schützen Sie nicht vor allen möglichen SQL-Injection-Angriffen. Für bestimmte obskure Randfälle.
Ich passe mich an diese Antwort um über PDO zu sprechen...
Die lange Antwort ist nicht so einfach. Sie basiert auf einem Angriff hier demonstriert .
Die Attacke
Beginnen wir also damit, den Angriff zu zeigen...
$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 wird mehr als 1 Zeile zurückgegeben. Sehen wir uns an, was hier vor sich geht:
-
Auswählen eines Zeichensatzes
$pdo->query('SET NAMES gbk');
Damit dieser Angriff funktioniert, müssen wir die Kodierung, die der Server auf der Verbindung erwartet, sowohl für die Kodierung '
wie bei ASCII, d.h. 0x27
und ein Zeichen zu haben, dessen letztes Byte ein ASCII-Zeichen ist \
d.h. 0x5c
. Wie sich herausstellt, werden in MySQL 5.6 standardmäßig 5 solcher Kodierungen unterstützt: big5
, cp932
, gb2312
, gbk
y sjis
. Wir wählen aus gbk
hier.
Nun ist es sehr wichtig, die Verwendung von SET NAMES
hier. Damit wird der Zeichensatz festgelegt AUF DEM SERVER . Es gibt noch einen anderen Weg, aber dazu kommen wir noch früh genug.
-
Die Nutzlast
Die Nutzlast, die wir für diese Injektion verwenden werden, beginnt mit der Byte-Sequenz 0xbf27
. Unter gbk
ist ein ungültiges Multibyte-Zeichen; in latin1
ist es die Zeichenkette ¿'
. Beachten Sie, dass in latin1
und gbk
, 0x27
ist für sich genommen eine wörtliche '
Charakter.
Wir haben diese Nutzlast gewählt, weil wir bei einem Aufruf von addslashes()
darauf, würden wir ein ASCII \
d.h. 0x5c
, vor dem '
Zeichen. Also würden wir am Ende 0xbf5c27
die in gbk
ist eine zweistellige Zeichenfolge: 0xbf5c
gefolgt von 0x27
. Oder mit anderen Worten, ein gültig Zeichen, gefolgt von einem nicht abgeschnittenen '
. Aber wir benutzen nicht addslashes()
. Also weiter zum nächsten Schritt...
-
$stmt->execute()
Das Wichtigste ist, dass PDO standardmäßig NO echte vorbereitete Erklärungen abgeben. Es emuliert sie (für MySQL). Deshalb baut PDO intern die Abfragezeichenfolge auf, indem es mysql_real_escape_string()
(die MySQL-C-API-Funktion) für jeden gebundenen Zeichenfolgenwert.
Der C-API-Aufruf an mysql_real_escape_string()
unterscheidet sich von addslashes()
dass es den Zeichensatz der Verbindung kennt. Daher kann es die Umformung für den Zeichensatz, den der Server erwartet, korrekt durchführen. Bis zu diesem Punkt denkt der Client jedoch, dass wir immer noch mit latin1
für die Verbindung, weil wir ihm nie etwas anderes gesagt haben. Wir haben dem Server wir verwenden gbk
aber die Kunde denkt immer noch, es sei latin1
.
Daher die Aufforderung an mysql_real_escape_string()
fügt den Backslash ein, und wir haben eine frei hängende '
Zeichen in unserem "entkommenen" Inhalt! In der Tat, wenn wir uns ansehen würden $var
im gbk
Zeichensatz, würden wir sehen:
' OR 1=1 /\*
Das ist genau das, was der Angriff erfordert.
-
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 haben gerade erfolgreich ein Programm mit PDO Prepared Statements angegriffen...
Die einfache Lösung
Es ist erwähnenswert, dass Sie dies verhindern können, indem Sie emulierte vorbereitete Anweisungen deaktivieren:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Dies wird in der Regel zu einer echten vorbereiteten Anweisung führen (d. h. die Daten werden in einem von der Abfrage getrennten Paket übermittelt). Beachten Sie jedoch, dass PDO stillschweigend Fallback um Statements zu emulieren, die MySQL nicht nativ vorbereiten kann: Diejenigen, die es kann, sind aufgelistet im Handbuch, aber achten Sie darauf, dass Sie die richtige Serverversion auswählen).
Die richtige Lösung
Das Problem hier ist, dass wir die C-API's nicht aufgerufen haben mysql_set_charset()
anstelle von SET NAMES
. Wenn wir das täten, wäre alles in Ordnung, vorausgesetzt, wir verwenden eine MySQL-Version seit 2006.
Wenn Sie eine frühere Version von MySQL verwenden, dann ist ein error en mysql_real_escape_string()
bedeutete, dass ungültige Multibyte-Zeichen, wie die in unserer Nutzlast, als einzelne Bytes behandelt wurden, um sie zu entschlüsseln selbst wenn der Kunde korrekt über die Verbindungskodierung informiert wurde und so würde dieser Angriff immer noch erfolgreich sein. Der Fehler wurde in MySQL behoben 4.1.20 , 5.0.22 y 5.1.11 .
Aber das Schlimmste ist, dass PDO
nicht die C-API für mysql_set_charset()
bis 5.3.6, also in früheren Versionen kann nicht Verhindern Sie diesen Angriff für jeden möglichen Befehl! Es ist jetzt als ein DSN-Parameter die verwendet werden sollten anstelle von SET NAMES
...
Die rettende Gnade
Wie eingangs erwähnt, muss die Datenbankverbindung mit einem angreifbaren Zeichensatz kodiert sein, damit dieser Angriff funktioniert. utf8mb4
es nicht anfällig und kann dennoch unterstützen jede Unicode-Zeichen: Sie könnten sich also dafür entscheiden, stattdessen dieses Zeichen zu verwenden - es ist aber erst seit MySQL 5.5.3 verfügbar. Eine Alternative ist utf8
die auch nicht anfällig und kann den gesamten Unicode unterstützen Basic Multilingual Plane .
Alternativ dazu können Sie die Option NO_BACKSLASH_ESCAPES
SQL-Modus, der (unter anderem) die Funktionsweise von mysql_real_escape_string()
. Wenn dieser Modus aktiviert ist, 0x27
wird ersetzt durch 0x2727
statt 0x5c27
und damit der Fluchtprozess kann nicht gültige Zeichen in einer der gefährdeten Kodierungen zu erzeugen, wo sie vorher nicht existierten (d. h. 0xbf27
ist noch 0xbf27
usw.), so dass der Server die Zeichenkette immer noch als ungültig zurückweisen wird. Siehe jedoch @eggyal's Antwort für eine andere Sicherheitslücke, die durch die Verwendung dieses SQL-Modus entstehen kann (wenn auch nicht mit PDO).
Sichere 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");
Weil der Serer erwartet, dass 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");
Denn wir haben den Zeichensatz richtig eingestellt, so dass 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 die emulierten vorbereiteten Erklärungen ausgeschaltet haben.
$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 den Zeichensatz richtig eingestellt haben.
$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();
Weil MySQLi ständig echte vorbereitete Anweisungen ausführt.
Einpacken
Wenn Sie:
- Verwenden Sie moderne Versionen von MySQL (spätes 5.1, alle 5.5, 5.6, etc) UND PDOs DSN-Zeichensatzparameter (in PHP 5.3.6)
O
- Verwenden Sie keinen anfälligen Zeichensatz für die Verbindungskodierung (Sie verwenden nur
utf8
/ latin1
/ ascii
/ etc)
O
- Aktivieren Sie
NO_BACKSLASH_ESCAPES
SQL-Modus
Sie sind 100% sicher.
Andernfalls sind Sie angreifbar. auch wenn Sie PDO Prepared Statements verwenden...
Apéndice
Ich habe langsam an einem Patch gearbeitet, um die Standardeinstellung so zu ändern, dass sie für eine zukünftige Version von PHP nicht mehr emuliert wird. Das Problem, auf das ich stoße, ist, dass eine Menge Tests abbrechen, wenn ich das tue. Ein Problem ist, dass emulierte prepares nur Syntaxfehler beim Ausführen ausgeben, aber echte prepares geben Fehler beim Vorbereiten aus. Das kann also zu Problemen führen (und ist Teil des Grundes, warum die Tests nicht funktionieren).
5 Stimmen
Besserer Ansatz 7. Nummer Antwort stackoverflow.com/questions/134099/