Предотвращение внедрения SQL в класс базы данных - PullRequest
5 голосов
/ 29 марта 2010

Я создаю класс базы данных и подумал, что было бы неплохо включить некоторую форму предотвращения 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() в моих основных кодовых блоках, я бы предпочел, чтобы это было в функции.

Спасибо.

Ответы [ 8 ]

1 голос
/ 30 марта 2010

Либо параметризованный, либо попробуйте построить класс 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 голос
/ 30 марта 2010

Весь смысл предотвращения внедрения кода заключается в том, что вы хотите различать ваши 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 голос
/ 30 марта 2010

Существует два подхода для предотвращения атаки с использованием SQL-инъекций: подход на основе черного списка и подход на основе белого списка.

Подход черного списка подразумевает, что вам нужно проверить всю строку запроса, определить нежелательный код и удалить его. Это чертовски сложно. Вместо этого используйте подход белого списка с использованием параметризованного SQL. Таким образом, вы будете уверены, что единственным запросом, который будет выполнен, будет тот, который вы намеренно создали с использованием своего кода, и любая попытка внедрения будет неудачной, поскольку все запросы внедрения будут частью параметра и, следовательно, не будут выполняться база данных. Вы пытаетесь найти способ предотвратить внедрение после того, как запрос уже создан, что косвенно означает подход, основанный на черном списке.

Попробуйте использовать параметризованный SQL в своем коде, один из принципов безопасного кодирования, адаптированный во всем мире.

1 голос
/ 29 марта 2010

Проблема в том, что к тому моменту, когда вы создали SQL-запрос, уже слишком поздно, чтобы можно было предотвратить внедрение путем поиска переменных - в противном случае он уже встроен в PHP.

Экранирование должносделать гораздо раньше, когда вы строите запросы.Вы можете использовать класс построения запросов.

Однако я бы рекомендовал другой подход - иметь слой, который предоставляет таблицу базы данных в качестве объекта, вот пример пользовательский объект , которыйпроисходит от базового класса сущностей в дБ , который обеспечивает полный интерфейс с базой данных, используя шаблон активной записи и шаблон итератора .

Я проиллюстрирую это несколькими примерами;здесь есть итераторы, так как вы можете абстрагироваться намного дальше и иметь несколько общих классов для извлечения данных.

Чтобы создать запись пользователя, используя описанный выше подход:

$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());
}
0 голосов
/ 30 марта 2010

Зачем изобретать велосипед? Просто расширьте PDO и используйте параметризованные запросы. Если вы не знаете, что это такое, прочитайте документ, который содержит много примеров, чтобы начать.

0 голосов
/ 30 марта 2010

Я, я решил эту проблему, добавив параметры в функцию запроса. Я обнаружил, что 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 голосов
/ 29 марта 2010

Чтобы сделать это правильно, вы должны санировать вещи при создании запроса или помещать их в свой класс для передачи параметров, независимых от основного запроса.

0 голосов
/ 29 марта 2010

Вся идея предотвращения атак с использованием SQL-инъекций состоит в том, чтобы запретить пользователям запускать собственный SQL-запрос. Здесь кажется, что вы хотите разрешить пользователям запускать собственный SQL, так зачем их ограничивать?

Или если вы не разрешаете пользователям передавать SQL в метод query (). Это слишком низкий уровень для реализации экранирующих параметров. Как вы уже поняли, вы хотите экранировать только параметры, а не все операторы SQL.

Если вы параметризуете SQL, тогда вы можете избежать только параметров. В этом случае я предполагаю, что пользователи могут влиять на значения параметров, а не на SQL.

Посмотрите на методы PDO bindParam() или Zend_DB query(), чтобы получить представление о том, как это реализовано в других интерфейсах базы данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...