PDO fetch () не возвращает то, что он должен [dублировать]

Почему я не могу передать имя таблицы в подготовленный оператор PDO?

$stmt = $dbh->prepare('SELECT * FROM :table WHERE 1');
if ($stmt->execute(array(':table' => 'users'))) {
    var_dump($stmt->fetchAll());
}

Есть ли другой безопасный способ вставить имя таблицы в SQL-запрос? С безопасностью я имею в виду, что я не хочу делать

$sql = "SELECT * FROM $table WHERE 1"
202
задан 18 September 2015 в 19:13

5 ответов

Использование первого не по своей сути более безопасно, чем последнее, вам необходимо дезинформировать ввод, является ли он частью массива параметров или простой переменной. Поэтому я не вижу ничего плохого в использовании последней формы с $table, если вы убедитесь, что содержание $table безопасно (alphanum plus underscores?), Прежде чем использовать его.

4
ответ дан 15 August 2018 в 16:43
  • 1
    Учитывая, что первый вариант не будет работать, вы должны использовать некоторую форму построения динамических запросов. – Noah Goodrich 8 October 2008 в 16:58
  • 2
    Да, упомянутый вопрос не будет работать. Я пытался описать, почему было не очень важно даже пытаться это сделать. – Adam Bellaire 8 October 2008 в 17:01

Я вижу, что это старый пост, но я нашел его полезным и думал, что поделюсь решением, аналогичным тому, что предложил @kzqai:

У меня есть функция, которая получает два параметра, такие как ...

function getTableInfo($inTableName, $inColumnName) {
    ....
}

Внутри я проверяю массивы, которые я установил, чтобы убедиться, что доступны только таблицы и столбцы с «благословленными» таблицами:

$allowed_tables_array = array('tblTheTable');
$allowed_columns_array['tblTheTable'] = array('the_col_to_check');

Затем проверка PHP перед работающий PDO выглядит как ...

if(in_array($inTableName, $allowed_tables_array) && in_array($inColumnName,$allowed_columns_array[$inTableName]))
{
    $sql = "SELECT $inColumnName AS columnInfo
            FROM $inTableName";
    $stmt = $pdo->prepare($sql); 
    $stmt->execute();
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
11
ответ дан 15 August 2018 в 16:43

Чтобы понять, почему привязка имени таблицы (или столбца) не работает, вы должны понять, как работают заполнители в подготовленных операциях: они не просто заменяются как строки (соответственно экранированные), а результирующий SQL выполняется. Вместо этого СУБД, попросившая «подготовить» заявление, содержит полный план запросов о том, как он будет выполнять этот запрос, включая те таблицы и индексы, которые он будет использовать, которые будут одинаковыми независимо от того, как вы заполняете заполнители. [ ! d0]

План для SELECT name FROM my_table WHERE id = :value будет тем же самым, что вы замените :value, но похожее подобное SELECT name FROM :table WHERE id = :value невозможно спланировать, потому что СУБД не знает, какую таблицу вы собираетесь выбрать from.

Это не то, что библиотека абстракции, такая как PDO, может или должна работать, либо потому, что она победит две ключевые цели подготовленных операторов: 1) позволить базе данных заранее решить, как запрос будет запущен и будет использовать один и тот же план несколько раз; и 2) для предотвращения проблем безопасности, отделяя логику запроса от ввода переменной.

126
ответ дан 15 August 2018 в 16:43
  • 1
    Правда, но не учитывает эмуляцию формулировки PDO для подготовки (которая может предположительно параметризовать идентификаторы объектов SQL, хотя я до сих пор согласен, что это, вероятно, не должно). – eggyal 28 December 2013 в 01:40
  • 2
    @eggyal Я предполагаю, что эмуляция направлена ​​на то, чтобы стандартная функциональность работала над всеми примерами СУБД, а не добавляла совершенно новые функции. Заполнитель для идентификаторов также должен иметь четкий синтаксис, который не поддерживается непосредственно СУБД. PDO является довольно низкоуровневой оболочкой и не предлагает, например, предложение и генерацию SQL для предложений TOP / LIMIT / OFFSET, поэтому это будет немного неуместно в качестве функции. – IMSoP 2 January 2014 в 01:53
  • 3
    +1 для ответа на «почему». Я подумал, что это, вероятно, почему, и это помогает мне лучше понять метод prepare(). – philtune 2 March 2016 в 21:22

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

class myPdo{
    private $user   = 'dbuser';
    private $pass   = 'dbpass';
    private $host   = 'dbhost';
    private $db = 'dbname';
    private $pdo;
    private $dbInfo;
    public function __construct($type){
        $this->pdo = new PDO('mysql:host='.$this->host.';dbname='.$this->db.';charset=utf8',$this->user,$this->pass);
        if(isset($type)){
            //when class is called upon, it stores column names and column types from the table of you choice in $this->dbInfo;
            $stmt = "select distinct column_name,column_type from information_schema.columns where table_name='sometable';";
            $stmt = $this->pdo->prepare($stmt);//not really necessary since this stmt doesn't contain any dynamic values;
            $stmt->execute();
            $this->dbInfo = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
    }
    public function pdo_param($col){
        $param_type = PDO::PARAM_STR;
        foreach($this->dbInfo as $k => $arr){
            if($arr['column_name'] == $col){
                if(strstr($arr['column_type'],'int')){
                    $param_type = PDO::PARAM_INT;
                    break;
                }
            }
        }//for testing purposes i only used INT and VARCHAR column types. Adjust to your needs...
        return $param_type;
    }
    public function columnIsAllowed($col){
        $colisAllowed = false;
        foreach($this->dbInfo as $k => $arr){
            if($arr['column_name'] === $col){
                $colisAllowed = true;
                break;
            }
        }
        return $colisAllowed;
    }
    public function q($data){
        //$data is received by post as a JSON object and looks like this
        //{"data":{"column_a":"value","column_b":"value","column_c":"value"},"get":"column_x"}
        $data = json_decode($data,TRUE);
        $continue = true;
        foreach($data['data'] as $column_name => $value){
            if(!$this->columnIsAllowed($column_name)){
                 $continue = false;
                 //means that someone possibly messed with the post and tried to get data from a column that does not exist in the current table, or the column name is a sql injection string and so on...
                 break;
             }
        }
        //since $data['get'] is also a column, check if its allowed as well
        if(isset($data['get']) && !$this->columnIsAllowed($data['get'])){
             $continue = false;
        }
        if(!$continue){
            exit('possible injection attempt');
        }
        //continue with the rest of the func, as you normally would
        $stmt = "SELECT DISTINCT ".$data['get']." from sometable WHERE ";
        foreach($data['data'] as $k => $v){
            $stmt .= $k.' LIKE :'.$k.'_val AND ';
        }
        $stmt = substr($stmt,0,-5)." order by ".$data['get'];
        //$stmt should look like this
        //SELECT DISTINCT column_x from sometable WHERE column_a LIKE :column_a_val AND column_b LIKE :column_b_val AND column_c LIKE :column_c_val order by column_x
        $stmt = $this->pdo->prepare($stmt);
        //obviously now i have to bindValue()
        foreach($data['data'] as $k => $v){
            $stmt->bindValue(':'.$k.'_val','%'.$v.'%',$this->pdo_param($k));
            //setting PDO::PARAM... type based on column_type from $this->dbInfo
        }
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);//or whatever
    }
}
$pdo = new myPdo('anything');//anything so that isset() evaluates to TRUE.
var_dump($pdo->q($some_json_object_as_described_above));

это просто пример, поэтому, разумеется, copy-> paste не будет работать. Отрегулируйте для ваших потребностей. Теперь это может не обеспечить 100% -ную защиту, но позволяет контролировать имена столбцов, когда они «входят» как динамические строки и могут быть изменены в конце пользователя. Кроме того, нет необходимости создавать некоторый массив с именами и типами столбцов таблицы, так как они извлекаются из information_schema.

0
ответ дан 15 August 2018 в 16:43

Часть меня интересует, если бы вы могли предоставить свою собственную настраиваемую функцию очистки так же просто, как это:

$value = preg_replace('/[^a-zA-Z_]*/', '', $value);

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

0
ответ дан 15 August 2018 в 16:43
  • 1
    Имена таблиц MySQL могут содержать другие символы. См. [D0] dev.mysql.com/doc/refman/5.0/en/identifiers.html – Phil 29 April 2014 в 06:31
  • 2
    Но должны ли они? ;) – Phil LaNasa 30 April 2015 в 22:56
  • 3
    @PhilLaNasa на самом деле некоторые защищают их (нужна ссылка). Поскольку большинство СУБД нечувствительны к регистру, сохраняя имя в недифференцированных символах, ex: MyLongTableName, это легко читать правильно, но если вы проверите сохраненное имя, оно (вероятно) будет MYLONGTABLENAME, которое не очень читаемо, поэтому MY_LONG_TABLE_NAME на самом деле более читабельна. – mloureiro 10 August 2015 в 20:26
  • 4
    Существует очень хорошая причина не иметь это как функцию: вы должны очень редко выбирать имя таблицы на основе произвольного ввода. Вы почти наверняка не хотите, чтобы злонамеренный пользователь мог заменить «пользователей». или "заказы" в Select * From $table. На самом деле здесь важно иметь белый список или строгое совпадение шаблонов (например, «начальный отчет с именами», за которым следует только от 1 до 3 цифр »). – IMSoP 21 March 2017 в 15:55

Другие вопросы по тегам:

Похожие вопросы: