Die kurze Antwort lautet ja, ja, es gibt einen Weg, um herumzukommenmysql_real_escape_string()
.
Für sehr OBSCURE EDGE CASES !!!
Die lange Antwort ist nicht so einfach. Es basiert auf einem hier demonstrierten Angriff .
Der Angriff
Beginnen wir also damit, den Angriff zu zeigen ...
mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
Unter bestimmten Umständen wird mehr als eine Zeile zurückgegeben. Lassen Sie uns analysieren, was hier los ist:
Auswählen eines Zeichensatzes
mysql_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 . Wenn wir den Aufruf der C-API-Funktion verwenden würden mysql_set_charset()
, wäre dies in Ordnung (bei MySQL-Versionen seit 2006). Aber mehr dazu in einer Minute ...
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 ...
mysql_real_escape_string ()
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 erfolgreich ein Programm mit mysql_real_escape_string()
...
Das Schlechte
Es wird schlimmer. PDO
Standardmäßig werden vorbereitete Anweisungen mit MySQL emuliert . Das bedeutet, dass auf der Client-Seite im Grunde genommen ein Sprintf mysql_real_escape_string()
(in der C-Bibliothek) durchgeführt wird, was bedeutet, dass Folgendes zu einer erfolgreichen Injektion führt:
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
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).
Das hässliche
Ich sagte ganz am Anfang, dass wir all dies hätten verhindern können, wenn wir mysql_set_charset('gbk')
stattdessen verwendet hätten SET NAMES gbk
. Und das stimmt, vorausgesetzt, Sie 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 .
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.
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
mysql_set_charset()
/ $mysqli->set_charset()
/ PDOs DSN-Zeichensatzparameter (in PHP ≥ 5.3.6).
ODER
- Verwenden Sie keinen anfälligen Zeichensatz für die Verbindungscodierung (Sie verwenden nur
utf8
/ latin1
/ ascii
/ etc).
Du bist 100% sicher.
Andernfalls sind Sie verwundbar , obwohl Siemysql_real_escape_string()
...