PHP:使用准备好的语句并防止 SQL 注入与逃逸

Posted

技术标签:

【中文标题】PHP:使用准备好的语句并防止 SQL 注入与逃逸【英文标题】:PHP: using prepared statements and protecting against SQL injection vs escape 【发布时间】:2012-07-12 09:05:08 【问题描述】:

我明白,准备好的语句是寻求 SQL 注入保护的最终方法。但是,它们以有限的方式提供覆盖;例如,如果我让用户决定按操作的顺序(即,它是 ASC 还是 DESC?等),则准备好的语句没有覆盖。

我知道我可以将用户输入映射到一个预定义的白名单。但是,这只有在可以预先创建或彻底猜测白名单时才有可能。

例如,在我上面提到的情况下(ASC 或 DESC),这可以很容易地根据接受值列表进行映射和验证。但是不存在无法对照白名单验证部分 SQL 语句的情况吗?

如果存在这种情况,那么推荐的方法是什么?

如果我要使用底层数据库的内置转义实用程序(例如 mysql 的 mysql_real_escape_string)来转义 user_input,我会在哪里失败?

我问这个问题的假设是我总是用引用的值构造我的 sql 语句——即使是整数......

让我们看看下面的例子并反思一下..

select $fields from $table where Age='$age' order by $orderby_pref

假设所有变量都是用户提供的。

如果我要 mysql_real_escape_string 上面 SQL 中的所有变量(而不是使用准备好的语句,它只覆盖了我一半,迫使我为另一半提供白名单,它无济于事),不是吗?同样安全(并且更容易编码)?如果不是,在哪种输入场景中转义实用程序会失败?

$fields       = mysql_escape($fields);
$table        = mysql_escape($table);
$age          = mysql_escape($age);
$orderby_pref = mysql_escape($orderby_pref);

select $fields from $table where Age='$age' order by $orderby_pref

【问题讨论】:

赞成是因为您了解并关心 sql 注入(而不是大多数人在 php 标记中提问) 动态sql和向查询中添加值是有区别的。绑定参数或字符串连接(加上转义)都可以用于添加值。动态 SQL 需要将可接受的语法列入白名单。因此,要么使用正则表达式、映射或 switch 语句来添加 DESCORDER 和其他限定符。不过,存储过程将是一个冗长的替代方案。 【参考方案1】:

无论您使用准备好的语句还是 mysql 转义函数,您都需要为表名或列名等内容使用白名单

问题是表名和列名没有用单引号或双引号引起来,所以如果你使用一个专门引用这些字符的函数(当然还有更多......),它不会对你的表做任何事情名字。

考虑表名my_table; DELETE * FROM mysql; SELECT * FROM my_table。此字符串中的任何内容都不会被 mysql 的转义函数转义,但它绝对是您想要检查白名单的字符串。

除此之外,mysql 转义函数在字符集方面存在问题,这会使它们变得无用,因此最好使用准备好的语句。

【讨论】:

您的my_table; DELETE * FROM mysql; SELECT * FROM my_table 示例确实很好地说明了为什么 MYSQL_ESCAPE 方法会失败......所以,简单来说,SQL 注入保护的唯一替代方案(对于准备好的语句或数据库的转义实用程序没有帮助的部分),是使用白名单方法。这个结论正确吗?【参考方案2】:

您可以使用 PDO,您的生活会变得更轻松...:

    #   Order
    switch(strtoupper($Order))
        default:
        case 'ASC':
            $Order = 'ASC';
            break;

        case 'DESC':
            $Order = 'DESC';
            break;
    

    #   ID
    $ID = 39;
    $Username = 'David';

    #   Query
    $Query = $this->DB->Main->prepare('SELECT * FROM Table WHERE ID = :ID AND Username = :Username ORDER BY HellBob '.$Order);
    $Query->bindValue(':ID', $ID, PDO::PARAM_INT);
    $Query->bindValue(':Username', $Username, PDO::PARAM_STR);

    #   All good ?
    if(!$Query->execute())
        exit('Error');
    

    // Results
    $Row = $Query->fetch(PDO::FETCH_ASSOC);

您不必担心引号或 SQL 注入。您可以使用您提到的简单“白名单”将变量添加到您的查询中。

【讨论】:

您忘记了订购字段 ;-) 这并不能真正回答问题,因为您在查询中硬编码了表名、列名和排序列。 那么,你可以只使用白名单来写表名。除了白名单,没有安全的方法。

以上是关于PHP:使用准备好的语句并防止 SQL 注入与逃逸的主要内容,如果未能解决你的问题,请参考以下文章

在 MySQL 中使用准备好的语句可以防止 SQL 注入攻击吗?

在codeigniter中逃避足够的防止sql注入的保护

这个准备好的语句可以防止 SQL 注入吗? [复制]

是否可以在没有准备好的语句(Node.js 和 MSSQL)的情况下防止 SQL 注入

如何将 MySQLi 准备好的语句与存储过程一起使用

准备好的语句中的动态 where 条件的 SQL 注入