Die kurze Antwort lautet NEIN . PDO-Vorbereitungen schützen Sie nicht vor allen möglichen SQL-Injection-Angriffen. Für bestimmte dunkle Randfälle.
Ich passe diese Antwort an, um über PDO zu sprechen ...
Die lange Antwort ist nicht so einfach. Es basiert auf einem hier demonstrierten Angriff .
Der Angriff
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 eine Zeile zurückgegeben. Lassen Sie uns analysieren, was hier los ist:
Auswählen eines Zeichensatzes
$pdo->query('SET NAMES gbk');
Für diesen Angriff zu arbeiten, müssen wir die Codierung , dass der Server die auf der Verbindung erwartet sowohl codieren '
als in ASCII dh 0x27
und einige Zeichen zu haben , deren letzte Byte ist ein ASCII \
dh 0x5c
. Wie sich herausstellt, gibt es 5 solche Codierungen in MySQL 5.6 standardmäßig unterstützt: big5
, cp932
, gb2312
, gbk
und sjis
. Wir werden gbk
hier auswählen .
Nun ist es sehr wichtig, die Verwendung von SET NAMES
hier zu beachten . Dies setzt den Zeichensatz auf dem Server . Es gibt noch einen anderen Weg, aber wir werden früh genug dort sein.
Die Nutzlast
Die Nutzlast, die wir für diese Injektion verwenden werden, beginnt mit der Bytesequenz 0xbf27
. In gbk
ist das ein ungültiges Multibyte-Zeichen. in latin1
, es ist die Zeichenfolge ¿'
. Beachten Sie, dass in latin1
und gbk
, 0x27
auf seinem eigenen ein wörtlicher ist '
Charakter.
Wir haben diese Nutzlast gewählt , weil, wenn wir aufgerufen addslashes()
darauf, würden wir eine ASCII einfügen \
dh 0x5c
, vor dem '
Charakter. Also haben wir mit aufzuwickeln würde 0xbf5c27
, die in gbk
eine zwei Zeichenfolge: 0xbf5c
gefolgt von 0x27
. Oder mit anderen Worten, ein gültiges Zeichen, gefolgt von einem nicht entflohenen '
. Aber wir benutzen nicht addslashes()
. Also weiter zum nächsten Schritt ...
$ stmt-> execute ()
Das Wichtigste dabei ist, dass PDO standardmäßig KEINE wirklich vorbereiteten Anweisungen ausführt. Es emuliert sie (für MySQL). Daher erstellt PDO intern die Abfragezeichenfolge und ruft mysql_real_escape_string()
(die MySQL C-API-Funktion) für jeden gebundenen Zeichenfolgenwert auf.
Der C-API-Aufruf von mysql_real_escape_string()
unterscheidet sich addslashes()
darin, dass er den Verbindungszeichensatz kennt. So kann die Escape-Anweisung für den vom Server erwarteten Zeichensatz ordnungsgemäß ausgeführt werden. Bis zu diesem Punkt glaubt der Client jedoch, dass wir immer noch latin1
für die Verbindung verwenden, da wir es nie anders gesagt haben. Wir haben dem Server mitgeteilt gbk
, dass wir ihn verwenden , aber der Client glaubt immer noch, dass dies der Fall ist latin1
.
Daher der Aufruf, mysql_real_escape_string()
den Backslash einzufügen, und wir haben einen frei hängenden '
Charakter in unserem "entkommenen" Inhalt! Wenn wir uns $var
den gbk
Zeichensatz ansehen würden, würden wir tatsächlich sehen:
縗 'OR 1 = 1 / *
Welches 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 ein Programm mit PDO Prepared Statements erfolgreich angegriffen ...
Die einfache Lösung
Nun ist es erwähnenswert, dass Sie dies verhindern können, indem Sie emulierte vorbereitete Anweisungen deaktivieren:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Dies führt normalerweise zu einer wirklich vorbereiteten Anweisung (dh die Daten werden in einem von der Abfrage getrennten Paket gesendet). Beachten Sie jedoch, dass PDO leise Rückfall auf Anweisungen emuliert , dass MySQL nicht nativ vorbereiten können: diejenigen , die es können , sind aufgelistet in dem Handbuch, aber passen Sie den entsprechenden Server - Version wählen).
Die richtige Lösung
Das Problem hierbei ist, dass wir nicht die C-APIs mysql_set_charset()
anstelle von aufgerufen haben SET NAMES
. In diesem Fall wäre alles in Ordnung, vorausgesetzt, wir verwenden seit 2006 eine MySQL-Version.
Wenn Sie eine frühere MySQL - Release verwenden, dann einen Fehler in mysql_real_escape_string()
gemeint , dass ungültige Mehrbytezeichen wie die in unserer Nutzlast als einzelnes Bytes behandelt wurden , für die Zwecke entkommen , auch wenn der Kunde hat richtig die Verbindung Codierung informiert worden und so dieser Angriff würde immer noch erfolgreich. Der Fehler wurde in MySQL 4.1.20 , 5.0.22 und 5.1.11 behoben .
Das Schlimmste ist PDO
jedoch, dass die C-API mysql_set_charset()
erst in Version 5.3.6 verfügbar gemacht wurde. In früheren Versionen kann sie diesen Angriff nicht für jeden möglichen Befehl verhindern! Es wird jetzt als DSN-Parameter angezeigt , der anstelle von SET NAMES
...
Die rettende Gnade
Wie eingangs erwähnt, muss die Datenbankverbindung mit einem anfälligen Zeichensatz codiert werden, damit dieser Angriff funktioniert. utf8mb4
ist nicht anfällig und kann dennoch jedes Unicode-Zeichen unterstützen. Sie können sich also dafür entscheiden, dieses zu verwenden. Es ist jedoch erst seit MySQL 5.5.3 verfügbar. Eine Alternative ist utf8
, die ebenfalls nicht anfällig ist und die gesamte mehrsprachige Unicode- Grundebene unterstützen kann .
Alternativ können Sie den NO_BACKSLASH_ESCAPES
SQL-Modus aktivieren , der (unter anderem) den Betrieb von ändert mysql_real_escape_string()
. Wenn dieser Modus aktiviert ist, 0x27
wird er durch 0x2727
und nicht ersetzt, 0x5c27
und daher kann der Escape-Prozess keine gültigen Zeichen in einer der anfälligen Codierungen erstellen, in denen sie zuvor nicht vorhanden waren (dh 0xbf27
immer noch 0xbf27
usw.). Daher lehnt der Server die Zeichenfolge weiterhin als ungültig ab . In der Antwort von @ eggyal finden Sie jedoch eine andere Sicherheitsanfälligkeit, die sich aus der Verwendung dieses SQL-Modus ergeben kann (allerdings 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 Server 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");
Weil wir den Zeichensatz richtig eingestellt haben, damit Client und 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 deaktiviert 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 wirklich vorbereitete Anweisungen ausführt.
Einpacken
Wenn du:
- Verwenden Sie moderne Versionen von MySQL (Ende 5.1, alle 5.5, 5.6 usw.) UND den DSN-Zeichensatzparameter von PDO (in PHP ≥ 5.3.6).
ODER
- Verwenden Sie keinen anfälligen Zeichensatz für die Verbindungscodierung (Sie verwenden nur
utf8
/ latin1
/ ascii
/ etc).
ODER
- Aktivieren Sie den
NO_BACKSLASH_ESCAPES
SQL-Modus
Du bist 100% sicher.
Andernfalls sind Sie anfällig , obwohl Sie PDO Prepared Statements verwenden ...
Nachtrag
Ich habe langsam an einem Patch gearbeitet, um die Standardeinstellung so zu ändern, dass keine Vorbereitungen für eine zukünftige Version von PHP emuliert werden. Das Problem, auf das ich stoße, ist, dass viele Tests dabei unterbrochen werden. Ein Problem ist, dass emulierte Vorbereitungen nur Syntaxfehler bei der Ausführung auslösen, echte Vorbereitungen jedoch Fehler bei der Vorbereitung. Das kann also Probleme verursachen (und ist Teil des Grundes, warum Tests nicht funktionieren).