【问题标题】:Preventing SQL injecting in a database class防止数据库类中的 SQL 注入
【发布时间】:2010-03-29 19:17:37
【问题描述】:

我正在构建一个数据库类,并认为结合某种形式的 SQL 注入预防是一个好主意(呵呵!)。这是运行数据库查询的方法:

class DB
{
    var $db_host    = 'localhost';
    var $db_user    = 'root';
    var $db_passwd  = '';
    var $db_name    = 'whatever';

    function query($sql)
    {
        $this->result = mysql_query($sql, $this->link);
        if(!$this->result)
        {
           $this->error(mysql_error());
        } else {
            return $this->result;
        }
    }
}

课堂上还有更多内容,但我只是为了这个而减少了它。我面临的问题是,如果我只使用mysql_real_escape_string($sql, $this->link);,那么它会转义整个查询并导致 SQL 语法错误。如何动态找到需要转义的变量?我想避免在我的主要代码块中使用mysql_real_escape_string(),我宁愿在函数中使用它。

谢谢。

【问题讨论】:

  • 我知道我在您之前的帖子中说过,但让我重复一遍:使用 PDO。如果你使用 PDO 和它的预处理语句,你甚至不必担心转义它们。
  • 使用参数化查询,使用 adodb 或 pdo。

标签: php sql mysql database security


【解决方案1】:

问题在于,当您构建 SQL 查询时,通过查找变量来防止注入已经太晚了 - 否则它已经内置到 PHP 中了。

在构建查询时需要更早地进行转义。您可以使用查询构建类。

但是我会推荐一种不同的方法 - 即有一个将数据库表作为对象提供的层,这里是一个示例 user object,它源自 base db entity class,它提供了一个完整的数据库接口,使用active record patterniterator pattern

我会用一些例子来说明这一点;这里的巧妙之处在于迭代器,因为您可以抽象出更多内容,并且可以进一步使用一些非常通用的类来提取数据。

使用上述方法创建用户记录:

$user = new DbUser();
$user->create();
$user->set_email('test@example.com');
$user->write();

读取用户记录:

$user = new DbUser();
$user->set_email('text@example.com');
if ($user->load_from_fields())
{
}

遍历记录:

$user_iterator = DbUser::begin();
if ($user_iterator->begin())
{
    do
    {
         $user = $user_iterator->current();
         echo $user->get_email();
    } while ($user_iterator->next());
}

【讨论】:

【解决方案2】:

有两种方法可以防止 SQL 注入攻击:基于黑名单的方法和基于白名单的方法。

黑名单方法意味着您需要检查整个查询字符串并识别不需要的代码并将其删除。这是非常困难的。 相反,通过使用参数化 SQL 来使用白名单方法。这样,您将确保唯一将执行的查询将是您有意使用您的代码构建的查询,并且任何注入尝试都将失败,因为所有注入查询都将成为参数的一部分,因此不会由数据库。 您正试图在查询已经建立后找到一种防止注入的方法,这间接意味着基于黑名单的方法。

尝试在您的代码中使用参数化 SQL,这是全球适用的安全编码原则之一。

【讨论】:

    【解决方案3】:

    参数化或尝试构建 DB 类,以便使用类似的东西传递 WHERE 的所有值(以及任何可能使用的值):

    $db->where(x,y);
    

    即。

    $db->where('userid','22');
    

    在类语料库中使用类似的东西

    function where(var x, var y) // method
    {
        $this->where .= x . ' = '.mysql_real_escape_string(y);
    }
    

    当然这需要清理以支持多个 WHERE 输入。

    【讨论】:

      【解决方案4】:

      防止代码注入的重点是您要区分您的 sql 和用户注入的 sql。在这个最低级别,你不能再这样做了。举个例子:

      select * from users where username='test' and password='itisme' or '4'='4'
      

      这似乎是完美的有效 sql,但它也可能是 sql 注入版本:

      "select * from users where username='test' and password='" . "itisme' or '4'='4". "'"
      

      所以你必须在你的代码中更进一步,或者像其他人建议的那样使用包装器。

      【讨论】:

        【解决方案5】:

        要以正确的方式执行此操作,您必须在构建查询时清理这些内容,或者将其放入您的类中以传递独立于核心查询的参数。

        【讨论】:

          【解决方案6】:

          防止 SQL 注入攻击的整个想法是防止用户运行自己的 SQL。在这里,您似乎希望允许用户运行自己的 SQL,那么为什么要限制它们呢?

          或者,如果您不允许用户将 SQL 传递给 query() 方法。这对于实现转义参数的级别太低了。正如您所意识到的,您只想转义参数,而不是整个 SQL 语句。

          如果您对 SQL 进行参数化,那么您可以只对参数进行转义。在这种情况下,我假设用户可以影响参数的值,而不是 SQL。

          查看 PDO 的 bindParam() 或 Zend_DB 的 query() 方法,了解它是如何在其他数据库接口中实现的。

          【讨论】:

          • 如果这将用于登录表单等(它将是)我不想相信用户输入非恶意 SQL。
          • “将用于登录表单”?注意 Bobby Tables...xkcd.com/327
          【解决方案7】:

          我,我通过在查询函数中添加参数解决了这个问题。 我发现 codeigniter 做得很好,所以我根据自己的喜好对其进行了调整。

          例子:

          $result = Database::query('INSERT INTO table (column1,column2,column3) VALUES(?,?,?)',array($value1,$value2,$value3));
          
          
          
          public static $bind_marker = '?';
          public static function query($query, $binds = FALSE)
              {
                  if($binds !== FALSE)
                  {
                      $query = self::compile_binds($query,$binds);
                  }
                  // $query now should be safe to execute
          }
          
          private static function compile_binds($query, $binds)
              {
                  if(strpos($query, self::$bind_marker) === FALSE)
                  {
                      return $query;
                  }
          
                  if(!is_array($binds))
                  {
                      $binds = array($binds);
                  }
          
                  $segments = explode(self::$bind_marker, $query);
          
                  if(count($binds) >= count($segments))
                  {
                      $binds = array_slice($binds, 0, count($segments)-1);
                  }
          
                  $result = $segments[0];
                  $i = 0;
                  foreach($binds as $bind)
                  {
                      if(is_array($bind))
                      {
                          $bind = self::sanitize($bind);
                          $result .= implode(',',$bind);
                      }
                      else
                      {
                          $result .= self::sanitize($bind);
                      }
          
                      $result .= $segments[++$i];
                  }
          
                  return $result;
              }
          
          public static function sanitize($variable)
          {
              if(is_array($variable))
              {
                  foreach($variable as &$value)
                  {
                      $value = self::sanitize($value);
                  }
              }
              elseif(is_string($variable))
              {
                  mysql_real_escape_string($variable);
              }
              return $variable;
          }
          

          我从 codeigniter 的版本中添加的主要补充是我可以使用数组作为参数,这对于使用“IN”很有用:

          $parameters = array
              (
                  'admin',
                  array(1,2,3,4,5)
              );
          
          $result = Database::query("SELECT * FROM table WHERE account_type = ? AND account_id IN (?)",$parameters);
          

          【讨论】:

            【解决方案8】:

            为什么要重新发明轮子? 只需扩展 PDO 并使用参数化查询。如果您不知道它是什么,请阅读包含大量示例的文档以开始使用。

            【讨论】:

              猜你喜欢
              • 2011-10-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-11-21
              • 2013-04-18
              • 2011-06-12
              • 1970-01-01
              相关资源
              最近更新 更多