在PHP中的SQL查询中将变量转换为整数

时间:2014-06-17 07:09:28

标签: php mysql sql sql-injection

首先, 我完全了解SQL injection vulnerabilities ,我正在将PDO用于我在PHP中开发的新应用程序。

长话短说,我工作的组织目前无法委托任何人力资源将所有人力资源转换为PDO以用于我目前正在开展的相当大的应用程序,所以我和#39; m在此期间坚持使用mysql_*函数。

无论如何,我想知道使用数据验证功能是否安全"消毒"插值查询中使用的数字参数。我们确实使用mysql_real_escape_string()作为字符串(是的,我也知道那里的限制)。这是一个例子:

public function foo($id) {
    $sql = "SELECT * FROM items WHERE item_id = $id";
    $this->query($sql); // call mysql_query and does things with result
}

$id通过HTTP GET识别用户提供的值,因此很明显此代码容易受到攻击。如果我这样做会好的吗?

public function foo($id) {

    if (!ctype_digit($id)) {
        throw new \InvalidArgumentException("ID must be numeric");
    }

    $sql = "SELECT * FROM items WHERE item_id = $id";
    $this->query($sql); // call mysql_query and does things with result
}

我很清楚,ctype_digit与检查\d+的正则表达式相同。

(还有filter_var($id, FILTER_VALIDATE_INT),但在松散类型的比较中,可能会返回int(0)评估为FALSE,所以我必须{ {1}}那里。)

这个临时解决方案有问题吗?

更新

  • 变量不仅包含主键,还包含=== FALSEbooleantinyintint等类型的任何字段,这意味着零是完美的值得接受的值。
  • 我们使用的是PHP 5.3.2

6 个答案:

答案 0 :(得分:3)

是的,如果你确实虔诚地使用正确的函数来验证数据,并且如果数据不符合预期,则正确地阻止查询运行,我没有看到任何漏洞。 ctype_digit目的非常有限且明确:

  

如果字符串文本中的每个字符都是十进制数字,则返回TRUE,否则返回FALSE

此功能基本上没有任何问题,因此使用起来非常安全。它甚至会在空字符串上返回false(自PHP 5.1起)。请注意,is_numeric 是如此值得信赖。我可能仍然会添加范围检查以确保数字在预期范围内,我不确定溢出整数会发生什么。如果您在检查后另外投入(int),则无法注射。

警告:与所有非原生参数化查询一样,如果您进入任何带有连接字符集的恶作剧,仍有机会注入。可能漏掉的字节范围受到ctype_digit的严格限制,但你永远不知道可以提出什么。

答案 1 :(得分:1)

使用mysql_real_escape_string并将$id包装在单引号中。单引号确保安全性并避免SQL注入的可能性。

例如,SELECT * FROM table WHERE id = 'escaped string'无法被黑客攻击:SELECT * FROM table WHERE id = 1; DROP table;因为'1; DROP table;'将被视为WHERE的输入参数。

答案 2 :(得分:1)

是的,它会起作用。如果值不是数字字符串,您的代码将引发异常,您只需捕获该异常并向用户显示错误消息。

请注意ctype_digit($foo)

  • 如果在PHP 5.1之前true为空,则会返回$foo(请参阅文档);
  • false间隔之外的所有int $foo值(ASCII数字)返回[48, 57]

如果您打算使用$foo

,那么 还需要检查string是否为非空ctype_digit($foo)

答案 3 :(得分:1)

  

长话短说,我工作的组织目前无法委托任何人力资源将所有人员转移到PDO

我不知道这里的问题在哪里。根据您发布的代码,您已经在使用某种DB包装器,并且已经计划更改每个数字参数的调用代码。为什么不改变该DB包装器以使其支持预处理语句,并改变调用代码来使用它?

旧的mysql ext不是问题 - 可以用它来模拟准备好的语句。

  

我完全了解SQL注入漏洞。

您的全面意识"有点夸张。不幸的是,大多数人不理解真正的注射源,以及准备好的声明的真正目的。

将数据与查询分开是一件好事,但完全没必要。虽然准备语句的真正价值在于其必然性,而不是手动格式化的基本任意性。

另一个你的错是对字符串的分离处理 - 它在查询中部分格式化(添加引号),部分在外部(转义特殊字符),这又是一次呼叫灾难。

当您决定坚持手动格式化时,请迟早享受您的注射。您的想法适用于人工,完全控制的沙箱示例。然而,在现实生活中,事情变得与众不同,很多人都在努力。您要求人们这样做,而不是要求程序格式化您的数据。带来所有明显的后果。

这让我想知道为什么PHP用户无法从他们的错误中吸取教训,并且仍然热切地设计实践,这在很久以前就被证明是不可靠的。

刚刚在你的推理中发现了另一个谬论

  通过HTTP GET

用户提供的值显然这段代码很容易受到攻击。

您必须了解任何无格式值会使此代码容易受到攻击,无论其HTTP GET,FTP PUT还是文件读取。它不仅是臭名昭着的用户输入和#34;必须正确格式化,但任何输入。这就是为什么必须使DB驱动程序成为格式化的唯一地方。 应该不是开发人员格式化数据而是程序。您的想法与这样的基石原则相矛盾。

答案 4 :(得分:0)

对于ctype_digit()的大多数整数值,

$id将返回false。如果要使用该函数,请先将其强制转换为字符串:

public function foo($id) {

    $id = (string)$id;

    if (!ctype_digit($id)) {
        throw new \InvalidArgumentException("ID must be numeric");
    }

    $sql = "SELECT * FROM items WHERE item_id = $id";
    $this->query($sql); // call mysql_query and does things with result
}

这是因为整数被解释为ASCII值。

答案 5 :(得分:-1)

我将intval()用于简单案例,尽管(int)显然占用的资源较少。例如:

$sql =
    "SELECT * FROM categories WHERE category_id = " .
    intval($_POST['id']) .
    " LIMIT 1";