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






Использование первого по своей сути не более безопасно, чем второе, вам необходимо дезинфицировать ввод, независимо от того, является ли он частью массива параметров или простой переменной. Поэтому я не вижу ничего плохого в использовании последней формы с $table, при условии, что вы убедитесь, что содержимое $table безопасно (буквы плюс подчеркивание?), Прежде чем использовать его.
Да, в упомянутом вопросе это не сработает. Я пытался объяснить, почему не так важно даже пытаться сделать это таким образом.
Имена таблиц и столбцов НЕ МОГУТ быть заменены параметрами в PDO.
В этом случае вам просто нужно отфильтровать и дезинфицировать данные вручную. Один из способов сделать это - передать сокращенные параметры функции, которая будет выполнять запрос динамически, а затем использовать оператор switch() для создания белого списка допустимых значений, которые будут использоваться для имени таблицы или имени столбца. Таким образом, никакие пользовательские данные никогда не попадают прямо в запрос. Так например:
function buildQuery( $get_var )
{
switch($get_var)
{
case 1:
$tbl = 'users';
break;
}
$sql = "SELECT * FROM $tbl";
}
Не оставляя регистр по умолчанию или используя регистр по умолчанию, который возвращает сообщение об ошибке, вы гарантируете, что будут использоваться только те значения, которые вы хотите использовать.
+1 за внесение в белый список вариантов вместо использования каких-либо динамических методов. Другой альтернативой может быть отображение приемлемых имен таблиц в массив с ключами, которые соответствуют потенциальному вводу пользователя (например, array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') и т. д.).
Читая это, мне приходит в голову, что приведенный здесь пример генерирует недопустимый SQL для неправильного ввода, потому что у него нет default. При использовании этого шаблона вы должны либо пометить один из ваших case как default, либо добавить явный случай ошибки, например default: throw new InvalidArgumentException;.
Я думал о простом if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Спасибо за идею.
Я скучаю по mysql_real_escape_string(). Может быть, здесь я могу сказать это без того, чтобы кто-то прыгнул и сказал: «Но вам это не нужно с PDO»
Другая проблема заключается в том, что имена динамических таблиц нарушают проверку SQL.
Чтобы понять, что привязка Почему к имени таблицы (или столбца) не работает, вы должны понимать, как работают заполнители в подготовленных операторах: они не просто подставляются в строки (с соответствующим экранированием), а результирующий SQL выполняется. Вместо этого СУБД, попросившая «подготовить» оператор, предлагает полный план запроса о том, как он будет выполнять этот запрос, в том числе, какие таблицы и индексы он будет использовать, которые будут одинаковыми независимо от того, как вы заполняете заполнители.
План для SELECT name FROM my_table WHERE id = :value будет таким же, что бы вы ни заменили на :value, но, казалось бы, похожий SELECT name FROM :table WHERE id = :value не может быть запланирован, потому что СУБД не знает, из какой таблицы вы на самом деле собираетесь выбирать.
Это не то, что библиотека абстракций, такая как PDO, также может или должна обойти, поскольку она нарушит 2 ключевые цели подготовленных операторов: 1) позволить базе данных заранее решить, как будет выполняться запрос, и использовать то же самое. планировать несколько раз; и 2) для предотвращения проблем с безопасностью путем отделения логики запроса от входной переменной.
Верно, но не учитывает эмуляцию инструкции PDO prepare (которая мог предположительно параметризует идентификаторы объектов SQL, хотя я все же согласен с тем, что, вероятно, этого не должно быть).
@eggyal Я предполагаю, что эмуляция нацелена на то, чтобы стандартная функциональность работала на всех разновидностях СУБД, а не на добавление совершенно новой функциональности. Заполнитель для идентификаторов также потребует отдельного синтаксиса, который напрямую не поддерживается ни одной СУБД. PDO является довольно низкоуровневой оболочкой и, например, не предлагает и генерацию SQL для предложений TOP / LIMIT / OFFSET, так что это было бы немного неуместно в качестве функции.
Я вижу, что это старый пост, но я нашел его полезным и подумал, что поделюсь решением, аналогичным тому, что предложил @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);
}
хорошо для краткого решения, но почему не только $pdo->query($sql)
В основном по привычке при подготовке запросов, которые должны связывать переменную. Также чтение повторных вызовов выполняется быстрее с выполнением здесь stackoverflow.com/questions/4700623/pdos-query-vs-execute
в вашем примере нет повторных вызовов
Часть меня задается вопросом, могли бы вы предоставить свою собственную функцию дезинфекции так просто, как это:
$value = preg_replace('/[^a-zA-Z_]*/', '', $value);
Я действительно не думал об этом, но похоже, что удаление чего-либо, кроме символов и подчеркиваний, может сработать.
Имена таблиц MySQL могут содержать другие символы. См. dev.mysql.com/doc/refman/5.0/en/identifiers.html
@PhilLaNasa на самом деле немного защищать они должны (нужна ссылка). Поскольку большинство СУБД нечувствительны к регистру, сохраняя имя в виде недифференцированных символов, например: MyLongTableName, его легко читать правильно, но если вы проверите сохраненное имя, это будет (вероятно) MYLONGTABLENAME, который не очень читается, поэтому MY_LONG_TABLE_NAME на самом деле более читабельный.
Есть очень веская причина не использовать это как функцию: очень и очень редко следует выбирать имя таблицы на основе произвольного ввода. Вы почти наверняка не хотите, чтобы злоумышленник подставил в Select * From $table «пользователей» или «бронирования». Белый список или строгое соответствие шаблону (например, «имена, начинающиеся с отчета_, за которыми следуют только 1–3 цифры») здесь действительно важны.
Что касается основного вопроса в этой ветке, другие сообщения прояснили, почему мы не можем привязать значения к именам столбцов при подготовке операторов, поэтому вот одно решение:
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));
Вышеупомянутое - всего лишь пример, поэтому само собой разумеется, что копирование-> вставка не будет работать. Отрегулируйте под свои нужды. Теперь это может не обеспечить 100% -ную безопасность, но позволяет некоторый контроль над именами столбцов, когда они «входят» как динамические строки и могут быть изменены на стороне пользователя. Кроме того, нет необходимости создавать какой-либо массив с именами и типами столбцов вашей таблицы, поскольку они извлекаются из information_schema.
(Поздний ответ, обратитесь к моему примечанию).
То же правило применяется при попытке создать «базу данных».
Вы не можете использовать подготовленный оператор для привязки базы данных.
То есть:
CREATE DATABASE IF NOT EXISTS :database
не будет работать. Вместо этого используйте список надежных отправителей.
Примечание: Я добавил этот ответ (как вики сообщества), потому что он часто используется для закрытия вопросов, где некоторые люди публиковали вопросы, похожие на этот, пытаясь привязать база данных, а не таблицу и / или столбец.
Учитывая, что первый вариант не сработает, вам придется использовать какую-либо форму динамического построения запросов.