Оператор PDO/prep/белый список/установить кодировку, достаточно ли это безопасно, чтобы предотвратить внедрение

Я перехожу с расширения mysql на PDO, и после прочтения всего, что я мог от вас, гуру в SO и в других местах, у меня остались некоторые остаточные сомнения. Я придумал следующее, чтобы обратиться к SQL-инъекции для типичного запроса. Мне просто интересно, достаточно ли этого, или, может быть, я немного переборщил с белым списком, прежде чем повторить это во всех своих приложениях.

Мне не ясно, правильно ли я сделал белый список, т.е. должен ли я также как-то сбежать.

Кроме того, я не уверен, должен ли я setAttribute эмулировать значение false для каждого запроса или только один раз для скрипта.

$link = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);

$link->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

            $arr_i=$arr_k='';
            $m_act=$v_act='Y';
            $table = array('prices', 'versions', 'models');
            $allowedTables = array('prices', 'versions', 'models');             
            $field = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
            $allowedFields = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
            if(count( array_diff($field, $allowedFields))==0 AND  count( array_diff($table, $allowedTables))==0){
            $sql = "SELECT COUNT(DISTINCT `" . $field[0] . "`) as ctmod FROM `" . $table[0] . "`
            INNER JOIN `" . $table[1] . "` USING (`" . $field[1] . "`)
            INNER JOIN `" . $table[2] . "` USING (`" . $field[0] . "`)
            WHERE `" . $field[2] . "` BETWEEN :arr_i AND :arr_k
            AND " . $field[3] . " = :m_act
            AND " . $field[4] . " = :v_act";
            $stmt = $link->prepare($sql);
            $stmt->bindParam(':arr_i', $arr_i, PDO::PARAM_INT);
            $stmt->bindParam(':arr_k', $arr_k, PDO::PARAM_INT);
            $stmt->bindParam(':m_act', $m_act, PDO::PARAM_STR);
            $stmt->bindParam(':v_act', $v_act, PDO::PARAM_STR);
            for ($i=0; $i < $ctpri; $i++){
            $k=$i+1;
            $arr_i=$arr_pri[$i]+1;
            $arr_k=$arr_pri[$k];
            $stmt->execute();
            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $ctmod[] = $r['ctmod'];
            }
            }
            }
            else{die();}

person BernardA    schedule 21.12.2013    source источник
comment
codereview.stackexchange.com   -  person Mihai    schedule 22.12.2013


Ответы (2)


Да, ваш код полностью защищен от SQL-инъекций. Молодец.

Хотя, как указывает @YourCommonSense, в приведенном вами примере нет причин вообще превращать имена таблиц и столбцов в переменные. Было бы проще просто вписать их в запрос буквально.

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


Единственные советы, которые я могу дать:

  • Всякая конкатенация строк с конечными двойными кавычками, использованием . и повторным запуском двойных кавычек делает код неопрятным, и может быть сложно отслеживать, какие двойные кавычки вы начали и какие остановили. Альтернативный стиль интерполяции строк PHP для переменных заключается в заключении их в фигурные скобки, как показано ниже:

    $sql = "SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
        INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
        INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
        WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
        AND `{$field[3]}` = :m_act
        AND `{$field[4]}` = :v_act";
    
  • И в качестве еще одной альтернативы вы можете использовать здесь документы, поэтому вам вообще не нужно беспокоиться об ограничении строки. Хорошо, если у вас есть буквальные двойные кавычки внутри вашей строки, потому что вам не нужно их экранировать:

    $sql = <<<GO
        SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
        INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
        INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
        WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
        AND `{$field[3]}` = :m_act
        AND `{$field[4]}` = :v_act
    GO;
    
  • Наконец, это не имеет ничего общего с внедрением SQL, но хорошей практикой является проверка возвращаемого значения от prepare() и execute(), потому что они возвращают false, если возникает ошибка при синтаксическом анализе или выполнении.

    if (($stmt = $link->prepare($sql)) === false) {
        trigger_error(PDO::errorInfo()[2], E_USER_ERROR);
    }
    

    (В этом примере используется синтаксис PHP 5.4 для разыменования массива, возвращаемого функцией.)

    Или же вы можете настроить PDO для генерации исключений, чтобы не не надо проверять.

    $link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
person Bill Karwin    schedule 22.12.2013
comment
Спасибо, теперь мне лучше. Пара комментариев: вам нужно убрать обратные кавычки в предложениях AND. Почему-то, наверное из-за того, что в строке есть точка (models.active), думаю, с ними не получится. Я ценю, что вы показали мне способ обработки ошибок, мне нужно многому научиться в этом. Один из них — где я могу найти исключения/ошибки, выдаваемые PDO/PHP. - person BernardA; 22.12.2013
comment
@BernardA, вы должны разделять имена таблиц и столбцов отдельно: `models`.`active`. Хотя разграничивать их в любом случае не нужно, потому что они не зарезервированы MySQL слова и не содержат специальных символов или пробелов. - person Bill Karwin; 22.12.2013
comment
Спасибо. Хорошо. Когда я думаю об этом, мне довольно сложно читать такой запрос. Вероятно, лучше использовать ключи в массивах с именем таблицы/поля. - person BernardA; 22.12.2013
comment
@YourCommonSense, я ответил на вопрос ОП в первой строке своего ответа. И, учитывая, сколько раз вы давали косвенные советы по хорошей практике кодирования, я удивлен вашим комментарием. - person Bill Karwin; 22.12.2013
comment
@YourCommonSense, еще одна мысль: сделать более удобным форматирование безопасного SQL выгодно для безопасности, потому что разработчики хотят оставаться продуктивными. Если мы покажем им, как они могут быть безопасными и продуктивными, они с большей вероятностью примут лучшие методы кодирования. - person Bill Karwin; 22.12.2013

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

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

Итак, вместо

AND " . $field[3] . " = :m_act

вы должны написать просто

AND versions.active = 'Y'

без какой-либо привязки или внесения в белый список.

Все, что вам нужно защитить, — это только динамические значения. Таким образом, вы должны использовать подготовленные операторы только для $arr_i и $arr_k. Все остальные части запроса должны быть записаны непосредственно в запрос, как вы делали это раньше.

person Your Common Sense    schedule 22.12.2013
comment
@YCS, если бы я знал, что все правильно, я бы не задавал вопрос с самого начала. Я ценю ваш вклад, но, конечно, не его тон. Если вы намерены сохранить тот же тон, я был бы признателен, если бы вы воздержались от ответов на мои вопросы в будущем. Тот факт, что я учусь, не означает, что я соглашусь с издевательствами. - person BernardA; 23.12.2013
comment
Я понятия не имею, что это за тон, о котором вы говорите. Нигде я не упомянул о ваших навыках или знаниях или какой-либо другой личной особенности. Все, что я сделал, это ответил на ваш вопрос, предоставив единственный надежный ответ. Так что, пожалуйста, обуздайте свое воображение. - person Your Common Sense; 23.12.2013
comment
@YCS, именно в этом проблема. Я видел ваши выступления. Вы делаете это так часто без отказа, что считаете, что это нормально. Ну, это не так. Если вы готовы обсудить это дальше, я предлагаю вам написать мне на мою электронную почту в профиле. Ненавижу беспокоить этим других ТАК. - person BernardA; 24.12.2013