【问题标题】:does PHP mysql_real_escape_string() protect database name?PHP mysql_real_escape_string() 是否保护数据库名称?
【发布时间】:2011-01-02 08:08:38
【问题描述】:

我知道 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";

这将删除数据库。是否还有其他我不知道的保护级别?

【问题讨论】:

  • 为什么还要接受用户输入的数据库名称?
  • 我们的数据集非常大,所以我们把它分成了一百个左右的表。 GET 变量确定页面应该查询哪个表。
  • 一种简单的方法是将该变量的值与白名单中的一组允许值进行比较,例如if( ! in_array($_GET['table'], $allowed_tables) die('HAX!').
  • @Brian:这听起来没有必要,而且存在严重的安全风险。你有多少行?
  • 我们有 52 张桌子。它们都有 100,000 和 1,000,000 行,选择查询的速度非常重要。

标签: php mysql sql-injection


【解决方案1】:

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

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

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

【讨论】:

  • 添加反引号几乎没有什么区别。您自己声明表名仍然需要以某种方式进行转义,因此在这种情况下,虽然良好的编码实践实际上没有添加任何反引号。更糟糕的是,表的名称被泄露给了用户。谁知道他们能从中推断出什么。逃避的问题是你无法知道未来可能会发现什么漏洞。
【解决方案2】:

你真的应该考虑绑定你的 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

【讨论】:

  • 您不能对表名或列名或 SQL 关键字使用查询参数。
【解决方案3】:

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

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

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

【讨论】:

  • 我不同意这个解决方案。您只是在增加开销,并在代码和数据库之间强制建立不必要的关联。每次添加表格时,都必须更新代码数组。您没有理由不能尝试使用动态表/字段名称进行查询,如果查询失败,您只需捕获错误并返回消息。
【解决方案4】:

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

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

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

【讨论】:

  • 原来我说你应该确保所有数据库名称都是纯文本并且不包含 ;字符,但事实证明数据库和表名可以有 ;他们名字中的字符。
【解决方案5】:

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

如果你想防止这种'hackish'事情,只需执行explode(' ', $db) 然后获取结果数组的[0]。这将得到第一部分(RealDatabase),仅此而已。

【讨论】:

  • 你确定 MySQL 不接受表名中的空格吗?我过去曾成功使用过它们。
【解决方案6】:

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

【讨论】:

  • 我知道逃避所有查询是一种很好的做法,但是在我制定的场景中,逃避是否足以防止攻击?
【解决方案7】:

如果您真的必须为您的数据库使用来自用户的 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 函数而无需检查。考虑起来太不安全了。

安全性太重要了,不能让懒惰支配。

【讨论】:

  • 这里的评论与我对 Micheal Madsen 的回答相同。您在代码和数据库之间强制建立不必要的链接,并且使用这种方法,您无法在没有另一个的情况下更新一个。只要您进行适当的转义,就没有理由不能尝试使用动态表/字段名称的查询,如果查询失败,您只需捕获错误并返回消息。显然准备好的查询更适合这种情况,但你没有理由不能使用常规的 MySQL 驱动程序来做到这一点。
  • 代码和数据库之间不必要的链接?除了存储过程(在这种情况下不适用),每次更改数据库时都必须更改代码。您确实意识到 OP 想要从用户那里获取他的表名。充其量是一个非常冒险的举动。准备好的查询不适用于动态表名,也不适用于存储过程。我的回答是满足 OP 的要求并提供一点安全性。您的评论与我的回答无关,与 Micheal 的回答无关
  • 只是对我上面评论的更正,您可以通过在存储过程中构建查询字符串来在存储过程中执行此操作,但是您又回到了原点,您仍然需要转义用户输入否则 sql 注入仍然是可能的。
  • @zombat 对任意字段的未经过滤的访问可能会导致问题。假设您有一个员工列表,并且顺序是动态的。 UI 使用它来按名字或姓氏或职位排序。但是通过修改参数,您可能可以按薪水排序,您可能不希望人们知道谁的薪水更高和更少
猜你喜欢
  • 1970-01-01
  • 2020-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
相关资源
最近更新 更多