【问题标题】:How can I make this query injection-proof? (PHP)我怎样才能使这个查询防注入? (PHP)
【发布时间】:2012-09-05 17:54:51
【问题描述】:

要点

我想执行一个 SQL 查询,该查询依赖于我的 GET 中可变数量的参数,而不容易受到 SQL 注入的攻击。

参数

我的网址可以这样形成:

https://www.example.com/index.php?param1=blah1,param2=blah2,param3=a,b,c

或者像这样:

https://www.example.com/index.php?param1=blah1,param2=blah2,param3=a,b,c,d,e,f,g

换句话说,param3 可以有可变数量的逗号分隔参数 a、b、c 等。

白名单

我检查以确保 a、b、c 等中的所有参数都正确。在我执行查询之前已在批准的白名单中。

// $valid_params is an array of pre-approved parameters.
$arr = explode(',', clean($_GET['param3']));
$params = Array();
foreach($arr as $param){
  if(in_array($param, $valid_params)){
    array_push($params, $param);
  }
}

查询

我这样设置我的数据库连接(使用 MySQL):

$db_connection = new PDO("mysql:host={$DB_HOST};dbname={$DB_NAME}",$DB_USER,$DB_PASS);
$db_connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

我想执行这样的查询(安全除外):

$comma_separated_params = implode(',',$params);
$result = $db_connection->query("SELECT {$comma_separated_params} FROM some_table");

目标

有谁知道我怎样才能安全有效地做到这一点?

【问题讨论】:

  • 好问题。您可以使用准备好的语句来确保它是安全的。高效的?已经很高效了吧?
  • 白名单已经非常安全了。
  • 在允许 HTTP GET 参数指示在查询中选择哪些字段时要非常谨慎。使用白名单绝对是一个不错的起点。我会尝试定义用例,并传递定义哪个用例有效的参数。然后为每个用例使用预构建的选择列表

标签: php mysql sql pdo sql-injection


【解决方案1】:

根据您对开销的关注,您可以只使用SELECT *,然后在 PHP 中过滤数组 - 如果参数从未发送到数据库,则没有注入空间。

但这并不是最优雅的解决方案。这是我的做法:

$comma_separated_params =
  implode(
    ",",
    array_map(
      function($a) {return "`".$a."`";},
      array_intersect(
        explode(",",$_GET['param3']),
        $valid_params
      )
    )
  )
);

单行奇迹(为清楚起见添加了换行符)将采用$_GET['param3'] 变量,用逗号分隔,将其与您的有效参数相交(而不是您的foreach 循环),将每个元素包装在反引号中(见下面的注释),最后用逗号将它们粘合在一起。

看,反引号允许您使用任何字符串作为字段名称。通常是允许关键字作为名称,但也可以允许带有空格的列名,等等。在反引号中唯一有意义的字符是反斜杠和反引号 - 可以安全地假设它们不存在,因为它们必须在您的 $valid_params 列表中才能达到这一点。

【讨论】:

    【解决方案2】:

    白名单是这里的方法。如果你只允许你已经明确定义的东西,你应该没问题。至于效率如何,这都是相对的。您使用的版本对于相对较小的列表(例如少于 100 列的列表)表现良好,所以我不会担心。

    使用 PDO 的奖励积分。

    您对“允许”列的定义可能与数据库中的实际内容存在差异。更宽松的规范可能是使用SHOW FIELDS 获取相关表的字段,并且只允许这些字段。

    【讨论】:

      【解决方案3】:

      如果您只允许在参数 3 中传递预定义值的特定列表,并且您正在将输入值与它们进行比较,我认为您没有任何注入暴露,因为您可以完全控制最终进入您的 $comma_seperated_params 变量的值。

      【讨论】:

        【解决方案4】:

        这需要一些工作才能完成,但使用参数绑定它看起来像这样:

        $binding = array();
        $selects = array();
        foreach ( $params as $value ) {
          $binding[] = ':' . $value; 
          $selects = '?';
        }
        
        $select = implode(',', $select);
        
        $result = $db_connection->prepare("SELECT $select FROM some_table"); 
        foreach ( $binding as $key => $bind ) {
          $result->bindParam($key, $bind, PDO::PARAM_STR);
        }
        
        $result->execute();
        

        【讨论】:

        • 这似乎是要走的路,尽管我的应用程序在使用您的方法后尝试 fetchAll 时超时(并且您的方法似乎有很多语法错误)。当我尝试循环获取时,我也超时了。
        【解决方案5】:

        PDO::prepare 会帮助你。这正是专家推荐的。永远不要使用mysql_real_escape_string (string)。始终使用准备好的语句。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-29
          • 1970-01-01
          • 1970-01-01
          • 2021-08-01
          • 1970-01-01
          • 2021-07-31
          • 1970-01-01
          相关资源
          最近更新 更多