Могут ли операторы PHP PDO принимать имя таблицы или столбца в качестве параметра? - PullRequest
222 голосов
/ 08 октября 2008

Почему я не могу передать имя таблицы в подготовленный оператор 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"

Ответы [ 7 ]

197 голосов
/ 08 октября 2008

Имена таблиц и столбцов не могут быть заменены параметрами в PDO.

В этом случае вы просто захотите отфильтровать и очистить данные вручную. Один из способов сделать это - передать сокращенные параметры в функцию, которая будет динамически выполнять запрос, а затем использовать оператор switch() для создания белого списка допустимых значений, которые будут использоваться для имени таблицы или имени столбца. Таким образом, никакой пользовательский ввод никогда не входит непосредственно в запрос. Так, например:

function buildQuery( $get_var ) 
{
    switch($get_var)
    {
        case 1:
            $tbl = 'users';
            break;
    }

    $sql = "SELECT * FROM $tbl";
}

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

132 голосов
/ 13 апреля 2013

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

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

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

12 голосов
/ 30 апреля 2013

Я вижу, что это старый пост, но я нашел его полезным и решил поделиться решением, аналогичным предложенному @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);
}
4 голосов
/ 08 октября 2008

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

2 голосов
/ 08 ноября 2018

(Поздний ответ, обратитесь к моей записке).

То же правило применяется при попытке создать "базу данных".

Нельзя использовать подготовленный оператор для привязки базы данных.

т.е:.

CREATE DATABASE IF NOT EXISTS :database

не будет работать. Вместо этого используйте список надежных отправителей.

Примечание: Я добавил этот ответ (как вики сообщества), потому что он часто использовался для закрытия вопросов, где некоторые люди публиковали вопросы, подобные этому, пытаясь связать базу данных 1017 * а не таблица и / или столбец.

0 голосов
/ 09 сентября 2014

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

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 голосов
/ 29 апреля 2014

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

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

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

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