防止数据库类中的SQL注入攻击

5

我正在构建一个数据库类,认为将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(),而是将其放在一个函数中。

谢谢。


4
我知道我在你之前的帖子中已经说过这句话,但是让我重申一下:使用PDO。如果您使用PDO及其预处理语句,就不必担心转义它们。 - ryeguy
使用参数化查询,可以使用adodb或pdo。 - rook
8个回答

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());
}

1
这不是一个坏主意。关键词在于“活动记录模式”。Zend Framework、Ruby on Rails和其他一些框架都提供了这种类型的功能。 - Marcus Adams
看一下这里在stackoverflow上的一些orm/active记录讨论,例如https://dev59.com/VHRB5IYBdhLWcg3w1Kr0 - VolkerK

1

可以使用参数化查询或构建 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输入。


1
整个防止代码注入的目的在于区分您的 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". "'"

所以你必须在代码中更高层次地处理它,或者像其他人建议的那样使用包装器。


1

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

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

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


0
我通过向查询函数添加参数来解决了这个问题。 我发现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);

0
整个防止 SQL 注入攻击的想法是防止用户运行他们自己的 SQL。在这里,似乎你想允许用户运行他们自己的 SQL,那么为什么要限制他们呢?
或者如果你不允许用户将 SQL 传递到 query() 方法中。这太低级了,无法实现转义参数。正如你所意识到的,你只想转义参数,而不是整个 SQL 语句。
如果你将 SQL 参数化,那么你就可以只转义参数。在这种情况下,我假设用户可以影响参数的值,而不是 SQL。
看一下 PDO 的 bindParam() 或 Zend_DB 的 query() 方法,以了解其他数据库接口中如何实现这一点。

如果这将用于登录表单等(它将被使用),我不想不得不信任用户输入非恶意的SQL。 - Joe
将要用于登录表单?小心Bobby Tables... http://xkcd.com/327/ - Simon

0
要以正确的方式完成这个任务,你必须在构建查询时对内容进行清理,或将其放入类中以独立于核心查询传递参数。

0
为什么要重复造轮子呢? 只需扩展PDO并使用参数化查询即可。如果您不知道它是什么,请阅读文档,其中包含很多示例供您入门。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接