如何在使用mysql_real_escape_string时进行SQL注入

32 浏览
0 Comments

如何在使用mysql_real_escape_string时进行SQL注入

即使使用mysql_real_escape_string()函数,是否仍有SQL注入的可能性?

考虑以下示例情况。SQL是像这样在PHP中构建的:

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

我听到过很多人告诉我,像这样的代码仍然是危险的,即使使用了mysql_real_escape_string()函数也可能被黑客攻击。但我无法想到任何可能的漏洞?

像这样的经典注入方式:

aaa' OR 1=1 --

是不起作用的。

您知道是否有可能的注入可以通过上述PHP代码?

admin 更改状态以发布 2023年5月22日
0
0 Comments

考虑以下查询:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()无法保护您免受此影响。
在查询中使用单引号(' ')来围绕您的变量会使您免受此影响。以下也是一种选择:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";

0
0 Comments

简答是,确实有方法可以绕过mysql_real_escape_string(),但仅适用于极其罕见的情况。

长答案不太容易解释。它基于一个攻击案例,在这里展示

攻击

那么,我们从攻击开始解释...

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");

在某些情况下,上述代码会返回多个行。让我们解释一下这是怎么回事:

  1. 选择字符集

    mysql_query('SET NAMES gbk');
    

    要使此攻击生效,我们需要将服务器在连接中所期望的编码,同时将'编码成ASCII即0x27以及将某些字符的最后一个字节设置为ASCII \0x5c。正好MySQL 5.6支持5种这样的编码: big5cp932gb2312gbksjis。我们选择这里的gbk

    现在,非常重要的是注意使用SET NAMES。这设置了字符集在服务器上。如果我们调用C API函数mysql_set_charset(),那么我们就没有问题(对于自2006年以来的MySQL版本)。但是等一下,我们再解释一下为什么...

  2. 载荷

    我们将用于此注入的负载以字节序列0xbf27开头。在gbk中,这是个无效的多字节字符;在latin1中,它是字符串¿'。请注意,在latin1gbk中,0x27单独存在是字面上的'字符。

    我们选择了此载荷,因为如果我们对其调用addslashes(),我们将在'字符之前插入ASCII \,即0x5c。因此,我们最终得到0xbf5c27,在gbk中是两个字符序列:0xbf5c后跟0x27。换句话说,一个有效字符后面跟一个未转义的'。但我们没有使用addslashes()。因此,我们进入下一步...

  3. mysql_real_escape_string()

    C API调用mysql_real_escape_string()addslashes()不同之处在于,它知道连接字符集。因此,它可以根据服务器期望的字符集正确执行转义。但是,到此为止,客户端仍然认为我们在连接中仍在使用latin1,因为我们没有告诉它其他的。我们确实告诉服务器我们正在使用gbk,但客户端仍然认为它是latin1

    因此,对mysql_real_escape_string()的调用会插入反斜杠,我们的"转义"内容中存在一个孤立的'字符!实际上,如果我们在gbk字符集中查看$var,我们会看到:

    这里的攻击可以通过 这种方式 实现。查询语句只是一种形式, 但是下面是渲染后的查询语句:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

    。恭喜您通过使用 mysql_real_escape_string() 成功攻击了程序...现在情况变得更糟了。 PDO 默认使用 MySQL 模拟准备声明。这意味着在客户端,基本上它通过 C 库中的 mysql_real_escape_string() 执行了 sprintf 操作,这意味着以下内容将导致成功注入:

    $pdo->query('SET NAMES gbk');
    $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
    $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
    

    。现在要注意的是,您可以通过禁用模拟准备声明来防止此操作:

    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    

    。这通常会导致真正的准备声明(即将数据从查询分离出单独的数据包)。但是,请注意,PDO 将静默地 fallback  到不能本地准备的 MySQL 的语句:在手册中列出的是其可预编译的语句,但请注意选择适当的服务器版本。如果我们使用了 mysql_set_charset('gbk') 而不是 SET NAMES gbk,我们可以防止所有这些攻击。这是真实的,如果您使用的是自 2006 年以来的 MySQL 版本。但是,如果您使用早期版本的 MySQL,则  mysql_real_escape_string() 中的错误会导致无效的多字节字符(例如我们的有效载荷中的字符)被视为转义时的单个字节,即使已经正确地通知客户端连接编码,因此此攻击仍将成功。此错误在 MySQL 4.1.20、 5.0.22 和 5.1.11 中得到了修复。然而,最糟糕的是,PDO 直到 5.3.6 才公开了 mysql_set_charset() 的 C API,因此在先前的版本中,无法为每个可能的命令防止此攻击!现在它以一个 DSN 参数的形式公开出来。如我们在开始时所说,为了使此攻击起作用,必须使用易受攻击的字符集对数据库连接进行编码。utf8mb4 不容易受到攻击,但可以支持每个 Unicode 字符:因此,您可以选择使用它,但自 MySQL 5.5.3 起才可用。另一种选择是 utf8,它也不容易受到攻击,并且可以支持整个 Unicode 基本多文种平面。

    或者,你可以启用NO_BACKSLASH_ESCAPES SQL 模式,这会更改mysql_real_escape_string()的操作方式。(在其他情况下)启用此模式,0x27将被替换为0x2727而不是0x5c27,因此转义过程不能在任何脆弱的编码中创建以前不存在的有效字符(即0xbf27仍然是0xbf27等)-因此服务器仍将拒绝无效的字符串。但是,请参阅@eggyal的答案,因为使用此SQL模式可能会引发不同的漏洞。

    安全的示例

    以下示例是安全的:

    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");
    

    因为服务器期望的是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");
    

    因为我们已经正确地设置了字符集,使客户端和服务器匹配。

    $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 /*"));
    

    因为我们已经关闭了模拟的预处理语句。

    $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 /*"));
    

    因为我们已经正确地设置了字符集。

    $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();
    

    因为MySQLi始终执行真正的预处理语句。

    总结

    如果你:

    • 使用现代版本的MySQL(5.1后期,所有的5.5、5.6等)AND mysql_set_charset() / $mysqli->set_charset() / PDO的DSN字符集参数(在PHP ≥5.3.6中)

    或者

    • 不使用易受攻击的连接编码字符集(只使用utf8/latin1/ascii/等)

    你是100%安全的。

    否则,即使你正在使用mysql_real_escape_string(),你仍然是易受攻击的...

0