Einleitung: Sollte man Angst vor mysql_real_escape_string() haben?
\n\nSie haben sicherlich den alten Spruch der PHP-Entwickler gehört: „mysql_real_escape_string() schützt vor allen SQL-Injection-Angriffen“. Stimmt das wirklich? Im Jahr 2024, wo PDO und prepared statements zum Standard geworden sind, wirkt dieses Thema archaisch. Aber Tausende von Entwicklern erben noch immer alte Projekte, in denen mysql_real_escape_string() die einzige Verteidigungslinie ist.
Auf Stack Overflow hat die Frage „SQL injection that gets around mysql_real_escape_string()“ Hunderttausende von Aufrufen erhalten. Warum? Weil die Antwort nicht offensichtlich ist. In diesem Artikel analysieren wir reale Szenarien, in denen die Maskierung nicht schützt, und vergleichen verschiedene Ansätze zum Schutz. Wenn Sie in PHP programmieren – lesen Sie aufmerksam.
\n\nWie funktioniert mysql_real_escape_string()
\n\nDie Funktion mysql_real_escape_string() (aus der Erweiterung mysql_*, mittlerweile deprecated) maskiert Sonderzeichen in einer Zeichenkette unter Berücksichtigung der aktuellen Verbindungskodierung. Sie fügt einen Backslash vor den Zeichen hinzu: ', ", \\, \\n, \\r, NULL und Control-Z.
<?php\n$connection = mysql_connect('localhost', 'user', 'pass');\nmysql_select_db('test', $connection);\n$unsafe = "O'Brien";\n$safe = mysql_real_escape_string($unsafe); // O\\'Brien\n$query = "SELECT * FROM users WHERE name = '$safe'";\n?>\n\nEs scheint zu funktionieren. Aber es gibt Nuancen.
\n\nSzenario 1: Problem mit der Kodierung (GBK / Big5)
\n\nDie bekannteste Methode zur Umgehung ist die Verwendung von Multibyte-Kodierungen wie GBK (Chinesisch) oder Big5. Wenn die Verbindung auf GBK eingestellt ist und Sie die Zeichenkette %bf%27 übergeben, passiert Magie.
In GBK ist das Byte 0xbf kein abgeschlossenes Zeichen. Wenn mysql_real_escape_string() auf ' (0x27) trifft, fügt es einen Backslash 0x5c hinzu. Es entsteht die Sequenz: 0xbf + 0x5c + 0x27. Aber in GBK ist 0xbf5c ein einzelnes gültiges Zeichen (z. B. das Schriftzeichen „縗“). Infolgedessen wird der Backslash „verschluckt“ und das Anführungszeichen bleibt ungeschützt!
<?php\n// Kodierung auf GBK setzen\nmysql_query("SET NAMES 'GBK'", $connection);\n$input = "%bf%27"; // Bytes: 0xbf 0x27\n$escaped = mysql_real_escape_string($input, $connection);\n// Ergebnis: 0xbf5c27 -> in GBK ist das "縗'" (Anführungszeichen bleibt)\n$query = "SELECT * FROM users WHERE id = '$escaped'";\n// Tatsächlich ausgeführt: SELECT * FROM users WHERE id = '縗''\n// Jetzt kann injiziert werden: ' OR 1=1 -- \n?>\n\nFazit: Wenn die Kodierung der Verbindung nicht mit der Kodierung der Daten übereinstimmt, kann mysql_real_escape_string() umgangen werden. Genau dieser Fehler wurde im bekannten PHP-Bugreport #47168 beschrieben.
Szenario 2: Verwendung numerischer Felder
\n\nDie Maskierung funktioniert nur mit Zeichenketten. Wenn Sie Daten in ein numerisches Feld ohne Anführungszeichen einfügen, hilft mysql_real_escape_string() nicht, weil der Wert nicht maskiert wird – er wird einfach so eingefügt, wie er ist.
<?php\n$id = mysql_real_escape_string($_GET['id']); // Angenommen, "1 OR 1=1"\n$query = "SELECT * FROM users WHERE id = $id"; // Ohne Anführungszeichen!\n// Gefahr: SELECT * FROM users WHERE id = 1 OR 1=1\n?>\n\nSelbst wenn Sie die Zeichenkette maskieren, ist dies im numerischen Kontext sinnlos – es gibt keine Anführungszeichen, also kann jeder SQL-Code eingeschleust werden.
\n\nFazit: Maskierung ersetzt keine Typprüfung. Für Zahlen verwenden Sie intval() oder ctype_digit().
Szenario 3: LIKE-Abfragen und Wildcard-Zeichen
\n\nmysql_real_escape_string() maskiert ' und \\, berührt aber nicht die Zeichen