“救命!我的mysql_real_escape_string()不工作了!”
如果你曾经搜索过“绕过mysql_real_escape_string()的SQL注入”,你并不孤单。这个Stack Overflow上的问题获得了数万次浏览,并成为了Web安全领域的经典案例。看起来,只要使用mysql_real_escape_string(),你就安全了。但事实并非如此。黑客总能找到漏洞,而开发者则为此愁白了头。
在本文中,我们将详细分析SQL注入如何绕过PHP中的mysql_real_escape_string()转义。你将看到真实的代码示例,理解为什么“老一套”的方法不再有效,并学习如何真正地进行防护。我们开始吧!
为什么mysql_real_escape_string()不是万能药?
首先,简单回顾一下历史。mysql_real_escape_string()函数出现在PHP 4和5时代,当时所有人都使用mysql_*扩展。它用于转义引号、反斜杠和控制字符等特殊字符。但问题在于,它只在单一编码的上下文中有效。
如果客户端和服务器端的编码不匹配,问题就来了。而如果你使用多字节编码(例如GBK或SJIS),这个函数甚至可能成为你最危险的敌人。
经典案例:多字节攻击
假设你的网站使用GBK(中文)编码,而你传递了以下字符串:
$_POST['name'] = "0xbf27"; // 转义后的引号还是单纯的引号?在GBK中,字符0xbf27是一个有效的中文字符。但是,如果mysql_real_escape_string()“看到”了0x27(单引号),它会用反斜杠将其转义为:0xbf5c27。问题在于,0xbf5c在GBK中也是一个有效字符!结果,单引号0x27仍然未被转义,黑客就可以插入自己的SQL代码。
// 易受攻击的代码$name = mysql_real_escape_string($_POST['name']);$query = "SELECT * FROM users WHERE name = '$name'";
// 如果输入:0xbf27// 函数返回:0xbf5c27// 在GBK中:中文字符 + 单引号// 查询变为:SELECT * FROM users WHERE name = '中文字符' OR 1=1 -- '瞧!注入成功了。这并非理论——此类漏洞在真实项目中确实被发现过。
使用mysql_real_escape_string()时的典型漏洞
让我们列举最常见的错误,这些错误使得转义可以被绕过:
- 错误的编码——如上面的例子。如果服务器认为数据是UTF-8,而客户端发送的是GBK,转义就会失效。
- 对数字字段使用时不加引号——如果你直接插入数字,转义将不起作用。例如:
SELECT * FROM users WHERE id = $id——黑客可以写入1 OR 1=1。 - LIKE查询——转义不处理
%和_字符。可以通过模式匹配进行盲注。 - 多次转义——如果你两次调用该函数,反斜杠会加倍,防护就会失效。
示例:对数字字段的攻击
// 易受攻击的代码$id = mysql_real_escape_string($_GET['id']);$query = "SELECT * FROM products WHERE id = $id"; // 不需要引号!
// 黑客输入:1 UNION SELECT username, password FROM users// 查询变为:SELECT * FROM products WHERE id = 1 UNION SELECT username, password FROM users这里的转义毫无用处——因为没有需要转义的引号。黑客直接插入自己的SQL代码。
现代防护方法(有效的方法)
恐怖故事讲完了——让我们来看看解决方案。如果你使用PHP编写代码,并且希望彻底告别SQL注入,以下是你的检查清单:
1. 使用PDO或MySQLi的预处理语句
这是