Mysql
 sql >> Datenbank >  >> RDS >> Mysql

SQL-Injection, die mysql_real_escape_string() umgeht

Die kurze Antwort ist ja, ja, es gibt eine Möglichkeit, mysql_real_escape_string() zu umgehen .#Für sehr obskure Randfälle!!!

Die lange Antwort ist nicht so einfach. Es basiert auf einem Angriff hier demonstriert .

Der Angriff

Fangen wir also damit an, 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 dadurch mehr als eine Zeile zurückgegeben. Sehen wir uns an, was hier vor sich geht:

  1. Auswählen eines Zeichensatzes

    mysql_query('SET NAMES gbk');
    

    Damit dieser Angriff funktioniert, benötigen wir die Codierung, die der Server für die Verbindung erwartet, um sowohl ' zu codieren wie in ASCII, also 0x27 und ein Zeichen zu haben, dessen letztes Byte ein ASCII \ ist also 0x5c . Wie sich herausstellt, werden in MySQL 5.6 standardmäßig 5 solcher Kodierungen unterstützt:big5 , cp932 , gb2312 , gbk und sjis . Wir wählen gbk aus hier.

    Nun ist es sehr wichtig, die Verwendung von SET NAMES zu beachten hier. Damit wird der Zeichensatz AUF DEM SERVER festgelegt . Wenn wir den Aufruf der C-API-Funktion mysql_set_charset() verwenden , wir wären in Ordnung (bei MySQL-Versionen seit 2006). Aber dazu gleich mehr...

  2. Die Nutzlast

    Die Nutzlast, die wir für diese Injektion verwenden werden, beginnt mit der Bytesequenz 0xbf27 . Im gbk , das ist ein ungültiges Multibyte-Zeichen; in latin1 , es ist die Zeichenfolge ¿' . Beachten Sie das in latin1 und gbk , 0x27 allein ist ein wörtlicher ' Zeichen.

    Wir haben diese Payload gewählt, weil wir addslashes() aufgerufen haben darauf würden wir einen ASCII \ einfügen also 0x5c , vor dem ' Charakter. Wir würden also bei 0xbf5c27 landen , die in gbk ist eine Folge aus zwei Zeichen:0xbf5c gefolgt von 0x27 . Oder mit anderen Worten, ein gültiges Zeichen gefolgt von einem ' ohne Escapezeichen . Aber wir verwenden nicht addslashes() . Also auf zum nächsten Schritt...

  3. mysql_real_escape_string()

    Der C-API-Aufruf von mysql_real_escape_string() unterscheidet sich von addslashes() , dass es den Verbindungszeichensatz kennt. So kann es das Escaping für den Zeichensatz, den der Server erwartet, ordnungsgemäß durchführen. Bis zu diesem Punkt denkt der Client jedoch, dass wir immer noch latin1 verwenden für die Verbindung, weil wir es nie anders gesagt haben. Wir haben es dem Server mitgeteilt wir verwenden gbk , sondern der Client denkt immer noch, dass es latin1 ist .

    Daher der Aufruf von mysql_real_escape_string() fügt den Backslash ein, und wir haben ein frei hängendes ' Charakter in unserem "entkommenen" Inhalt! In der Tat, wenn wir uns $var ansehen würden im gbk Zeichensatz, würden wir sehen:

    縗' OR 1=1 /*

    Das ist genau 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 haben gerade erfolgreich ein Programm mit mysql_real_escape_string() angegriffen ...

Das Böse

Es wird schlimmer. PDO standardmäßig auf emulieren vorbereitete Anweisungen mit MySQL. Das bedeutet, dass es auf der Clientseite im Grunde einen Sprint durch mysql_real_escape_string() macht (in der C-Bibliothek), 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 wird normalerweise führen zu einer echten vorbereiteten Anweisung (d. h. die Daten werden in einem separaten Paket von der Abfrage gesendet). Beachten Sie jedoch, dass PDO stillschweigend Fallback wird Anweisungen zu emulieren, die MySQL nicht nativ vorbereiten kann:diejenigen, die es kann, sind aufgelistet im Handbuch, aber achten Sie darauf, die richtige Serverversion auszuwählen).

Das Hässliche

Ich habe ganz am Anfang gesagt, dass wir das alles hätten verhindern können, wenn wir mysql_set_charset('gbk') verwendet hätten statt SET NAMES gbk . Und das gilt, sofern Sie eine MySQL-Version seit 2006 verwenden.

Wenn Sie eine frühere MySQL-Version verwenden, dann ein Fehler in mysql_real_escape_string() bedeutete, dass ungültige Multibyte-Zeichen wie die in unserer Nutzlast als einzelne Bytes für Escaping-Zwecke behandelt wurden, auch wenn der Client korrekt über die Verbindungscodierung informiert wurde und so würde dieser Angriff noch gelingen. Der Fehler wurde in MySQL 4.1.20 behoben , 5.0.22 und 5.1.11 .

Aber das Schlimmste ist dieses PDO hat die C-API für mysql_set_charset() nicht verfügbar gemacht bis 5.3.6, also in früheren Versionen nicht Verhindern Sie diesen Angriff für jeden möglichen Befehl! Er ist jetzt als verfügbar DSN-Parameter .

Die rettende Gnade

Wie wir eingangs gesagt haben, muss die Datenbankverbindung mit einem anfälligen Zeichensatz codiert werden, damit dieser Angriff funktioniert. utf8mb4 ist nicht angreifbar und kann doch jeden unterstützen Unicode-Zeichen:Sie könnten es also stattdessen verwenden – aber es ist erst seit MySQL 5.5.3 verfügbar. Eine Alternative ist utf8 , die ebenfalls nicht anfällig ist und kann den gesamten Unicode Basic Multilingual Plane unterstützen .

Alternativ können Sie NO_BACKSLASH_ESCAPES aktivieren SQL-Modus, der (unter anderem) die Funktionsweise von mysql_real_escape_string() verändert . Wenn dieser Modus aktiviert ist, 0x27 wird durch 0x2727 ersetzt statt 0x5c27 und somit kann der Escape-Prozess nicht Erstellen Sie gültige Zeichen in einer der anfälligen Codierungen, in denen sie zuvor nicht existierten (z. B. 0xbf27 ist immer noch 0xbf27 usw.) – der Server weist die Zeichenfolge also weiterhin als ungültig zurück. Siehe jedoch @eggyals Antwort für eine andere Schwachstelle, die durch die Verwendung dieses SQL-Modus entstehen 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 utf8 erwartet ...

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 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 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 die ganze Zeit echte vorbereitete Anweisungen macht.

Abschluss

Wenn Sie:

  • Verwenden Sie moderne Versionen von MySQL (später 5.1, alle 5.5, 5.6 usw.) UND mysql_set_charset() / $mysqli->set_charset() / 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 / usw.)

Sie sind 100 % sicher.

Andernfalls sind Sie anfällig, obwohl Sie mysql_real_escape_string() verwenden ...