Introduction: Should you be afraid of mysql_real_escape_string()?
\n\nYou have probably heard the old saying among PHP developers: “mysql_real_escape_string() saves you from all SQL injections”. Is that really the case? In 2024, when PDO and prepared statements have become the standard, this topic seems archaic. But thousands of developers still inherit old projects where mysql_real_escape_string() is the only line of defense.
On Stack Overflow, the question “SQL injection that gets around mysql_real_escape_string()” has garnered hundreds of thousands of views. Why? Because the answer is not obvious. In this article, we will examine real scenarios where escaping does not help, and compare different approaches to protection. If you write in PHP — read carefully.
\n\nHow mysql_real_escape_string() works
\n\nThe function mysql_real_escape_string() (from the mysql_* extension, now deprecated) escapes special characters in a string, taking into account the current connection encoding. It adds a backslash before the characters: ', ", \\, \\n, \\r, NULL, and 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\nIt seems to work. But there are nuances.
\n\nScenario 1: The encoding problem (GBK / Big5)
\n\nThe most well-known way to bypass it is using multibyte encodings such as GBK (Chinese) or Big5. If the connection is set to GBK and you pass the string %bf%27 — magic happens.
In GBK, the byte 0xbf is not a complete character. When mysql_real_escape_string() encounters ' (0x27), it adds a backslash 0x5c. The result is the sequence: 0xbf + 0x5c + 0x27. But in GBK, 0xbf5c is a single valid character (for example, the Chinese character «縗»). As a result, the backslash is “consumed,” and the quote remains unescaped!
<?php\n// Setting the encoding to GBK\nmysql_query("SET NAMES 'GBK'", $connection);\n$input = "%bf%27"; // Bytes: 0xbf 0x27\n$escaped = mysql_real_escape_string($input, $connection);\n// Result: 0xbf5c27 -> in GBK this is "縗'" (the quote remains)\n$query = "SELECT * FROM users WHERE id = '$escaped'";\n// Actually executes: SELECT * FROM users WHERE id = '縗''\n// Now you can inject: ' OR 1=1 -- \n?>\n\nConclusion: If the connection encoding does not match the data encoding, mysql_real_escape_string() can be bypassed. This exact bug was described in the well-known PHP bug report #47168.
Scenario 2: Using numeric fields
\n\nEscaping only works with strings. If you insert data into a numeric field without quotes, mysql_real_escape_string() will not help because the value is not escaped — it is simply inserted as is.
<?php\n$id = mysql_real_escape_string($_GET['id']); // Let's say "1 OR 1=1"\n$query = "SELECT * FROM users WHERE id = $id"; // Without quotes!\n// Danger: SELECT * FROM users WHERE id = 1 OR 1=1\n?>\n\nEven if you escape the string, it is meaningless in a numeric context — there are no quotes, so any SQL code can be injected.
\n\nConclusion: Escaping does not replace data type validation. For numbers, use intval() or ctype_digit().
Scenario 3: LIKE queries and wildcard characters
\n\nmysql_real_escape_string() escapes ' and \\, but does not touch the characters