PHP mysql_real_escape_string()保护数据库名称吗?

时间:2010-01-07 02:49:00

标签: php mysql sql-injection

我知道mysql_real_escape_string()
     将反斜杠添加到以下字符:\ x00,\ n,\ r,\,',“和\ x1a

我知道这是如何保护查询不被注入到where子句中的变量之类的。但这是我不确定的情景:

$query = "SELECT * FROM $db WHERE 1";

如果从用户输入获取$ db,则用户可以插入如下内容:
$ db = 'RealDatabase WHERE 1; DELETE FROM RealDatabase WHERE 1; SELECT FROM RealDatabase';

根据我的理解,mysql_real_escape_string()不会影响这个字符串, 进行最终查询: $query = "SELECT * FROM RealDatabase WHERE 1; DELETE FROM RealDatabase WHERE 1; SELECT FROM RealDatabase WHERE 1";

将删除数据库。我还没有意识到另一个保护级别吗?

7 个答案:

答案 0 :(得分:8)

您正在寻找的保护级别由反引号

提供
"SELECT * FROM `$db` WHERE 1";

反引号用于qualify identifiers,否则可能是不明确的(即。MySQL reserved words),如果您接受用户输入或具有可变命名的列或数据库,则绝对应该使用反引号,或者我可以保证你将来会遇到麻烦。例如,如果您的系统使用某些用户输入创建了临时字段名称,那么只有该字段最终被命名为update

"SELECT field1,field2,update FROM table;"

悲惨地失败了。但是:

"SELECT `field`,`field2`,`update` FROM table"

工作得很好。 (这实际上是几年前我曾经处理过这个问题的系统的一个真实例子。)

这解决了你输入错误SQL的问题。例如,以下查询将只返回“未知列”错误,其中test; DROP TABLE test是注入的攻击代码:

"SELECT * FROM `test; DROP TABLE test`;"

请注意:使用反引号仍然可以进行SQL注入!

例如,如果您的$db变量包含在其中有反引号的数据,您仍然可以以正常方式注入一些SQL。如果你正在为数据库和字段名称使用可变数据,你应该在将它放入语句之前删除所有反引号,然后在内部使用反引号对其进行限定。

$db = str_replace('`','',$db);
$sql = "SELECT * FROM `$db` WHERE 1";

我利用数据库包装器,它具有单独的功能来清理数据和清理数据库标识符,这就是后者的作用:)

答案 1 :(得分:1)

您应该真正研究绑定SQL查询。

这将保护您免受基本所有 SQL注入。归结为:

(取自PHP.net)

$stmt = mssql_init('NewUserRecord');

// Bind the field names
mssql_bind($stmt, '@username',  'Kalle',  SQLVARCHAR,  false,  false,  60);

// Execute
mssql_execute($stmt);

PHP支持基本上所有数据库的绑定查询。哦,当然你应该仍然清理所有输入和放大器输出(显示)。

更多信息:   - http://php.net/manual/en/function.mssql-bind.php

答案 2 :(得分:1)

您可以创建一个单独的数据库名称和ID表,而不是在get查询中插入数据库名称。然后仅将 id 附加到查询中。然后,您可以查找该ID的相应数据库名称并使用它。然后,您可以确保收到的ID是数字(is_numeric),并且您还可以确定用户只能从列表中的数据库中进行选择。

(此外,这将阻止用户查找数据库的名称,并可能在您网站上的SQL注入中的其他位置使用它们。)

使用第一种方法解析数据库名称,然后在查询中使用它,并确保它不包含空格。

答案 3 :(得分:1)

不,mysql_real_escape_string在这里不会帮到你。该函数不是上下文敏感的(它不能,因为它没有任何上下文),这是一个完全不同的威胁模型。

您需要验证表是否存在,而不是将用户输入的表名直接发送到服务器。最好的解决方案是使用服务器端数组/查找表,其中包含允许使用的表名。如果他们试图使用那些不在那里的东西,那就不要让它们。

如果你真的需要所有的表,那么你可以问服务器“你有什么表?”并运行它的输出(可选地将其缓存一段时间以防止每次都询问服务器) - 但是很可能,最终你会有一个你不想要的表,然后你需要无论如何要使用数组的东西,所以就这样做吧。

答案 4 :(得分:0)

由于表名不接受空格字符,因此只需删除它们即可。这将使上面的$ DB RealDatabaseWHERE1;DELETEFROMRealDatabase....。这样会使查询无效,但可以防止这个缺陷。

如果你想阻止这种“hackish”的事情,只需做explode(' ', $db)然后得到结果数组的[0]。这将获得第一部分(RealDatabase)而不是其他任何内容。

答案 5 :(得分:0)

只要您使用有问题的数据,最好随时使用它。如果你自己指定表并且没有篡改的余地,则无需逃避它。如果您的用户决定可能作为查询运行的任何内容,请将其撤消。

答案 6 :(得分:0)

如果你真的必须使用来自用户的get(坏坏),那么请使用以下编码风格......

$realname = '';
switch ($_GET['dbname']){
    case 'sometoken' : $realname = 'real_name'; break;
    case 'sometoken1' : $realname = 'real_name1'; break;
    case 'sometoken2' : $realname = 'real_name2'; break;
    case 'sometoken3' : $realname = 'real_name3'; break;
    case 'sometoken4' : $realname = 'real_name4'; break;
    case default : die ('Cheeky!!!');
}

$query = "SELECT * FROM `{$realname}` WHERE 1";

或者......

$realname = $tablenames[$_GET['dbname']];

if (!$realname)
    die ('Cheeky!!!');

使用这两种方式或类似的编码可以保护您的输入免受意外值的影响。

这也意味着用户永远不会看到他们可以从中推断信息的真实表或数据库名称。

确保检查$ _GET ['dbname']的内容以确保它首先有效,否则将发出警告。

我仍然说这是一个非常糟糕的设计,它让人联想到允许用户提供文件名并将其传递给I / O函数而不进行检查。考虑起来太不安全了。

安全太重要了,不能让懒惰统治。