Почему я не могу передать имя таблицы в подготовленную инструкцию 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"
См. Следующее: http://us3.php.net/manual/en/book.pdo.php#69304
Имена таблиц и столбцов не могут быть заменены параметрами в PDO.
В этом случае вы просто захотите фильтровать и дезинфицировать данные вручную. Один из способов сделать это – передать сокращенные параметры функции, которая будет выполнять запрос динамически, а затем использовать оператор switch () для создания белого списка допустимых значений, которые будут использоваться для имени таблицы или имени столбца. Таким образом, пользовательский ввод никогда не попадает непосредственно в запрос. Так, например:
function buildQuery( $get_var ) { switch($get_var) { case 1: $tbl = 'users'; break; } $sql = "SELECT * FROM $tbl"; }
Не оставляя случая по умолчанию или используя случай по умолчанию, который возвращает сообщение об ошибке, вы гарантируете, что будут использоваться только те значения, которые вы хотите использовать.
Чтобы понять, почему привязка имени таблицы (или столбца) не работает, вы должны понять, как работают заполнители в подготовленных операциях: они не просто заменяются как строки (соответственно экранированные), а результирующий SQL выполняется. Вместо этого СУБД, попросив «подготовить» заявление, содержит полный план запроса того, как он будет выполнять этот запрос, включая те таблицы и индексы, которые он будет использовать, которые будут одинаковыми независимо от того, как вы заполняете заполнители.
План SELECT name FROM my_table WHERE id = :value
будет таким же, как вы его замените :value
, но похожее похожее SELECT name FROM :table WHERE id = :value
не может быть запланировано, потому что СУБД не знает, какая таблица вы на самом деле собираюсь выбрать.
Это не то, что библиотека абстракции, такая как PDO, может или должна работать, так как она победит две ключевые цели подготовленных операторов: 1) позволить базе данных заранее решить, как будет выполняться запрос, и использовать тот же планировать несколько раз; и 2) для предотвращения проблем безопасности, отделяя логику запроса от ввода переменной.
Я вижу, что это старый пост, но я счел его полезным и подумал, что я поделюсь решением, аналогичным тому, что предложил @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); }
Использование первого не является по своей сути более безопасным, чем последнее, вам нужно дезинформировать ввод, является ли он частью массива параметров или простой переменной. Поэтому я не вижу ничего плохого в использовании последней формы с помощью $table
, если вы убедитесь, что содержимое $table
безопасно (alphanum plus underscores?) Перед ее использованием.
Часть меня задается вопросом, можете ли вы предоставить свою собственную функцию санирования так же просто, как это:
$value = preg_replace('/[^a-zA-Z_]*/', '', $value);
Я действительно не продумал это, но кажется, что он удаляет все, кроме символов, и подчеркивания могут работать.
Что касается основного вопроса в этом потоке, другие сообщения дали понять, почему мы не можем привязывать значения к именам столбцов при подготовке операторов, так что вот одно из решений:
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.