Почему я не могу передать имя таблицы в подготовленный оператор 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 безопасно (alphanum plus underscores?), Прежде чем использовать его.
Я вижу, что это старый пост, но я нашел его полезным и думал, что поделюсь решением, аналогичным тому, что предложил @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);
}
Чтобы понять, почему привязка имени таблицы (или столбца) не работает, вы должны понять, как работают заполнители в подготовленных операциях: они не просто заменяются как строки (соответственно экранированные), а результирующий SQL выполняется. Вместо этого СУБД, попросившая «подготовить» заявление, содержит полный план запросов о том, как он будет выполнять этот запрос, включая те таблицы и индексы, которые он будет использовать, которые будут одинаковыми независимо от того, как вы заполняете заполнители. [ ! d0]
План для SELECT name FROM my_table WHERE id = :value будет тем же самым, что вы замените :value, но похожее подобное SELECT name FROM :table WHERE id = :value невозможно спланировать, потому что СУБД не знает, какую таблицу вы собираетесь выбрать from.
Это не то, что библиотека абстракции, такая как PDO, может или должна работать, либо потому, что она победит две ключевые цели подготовленных операторов: 1) позволить базе данных заранее решить, как запрос будет запущен и будет использовать один и тот же план несколько раз; и 2) для предотвращения проблем безопасности, отделяя логику запроса от ввода переменной.
Что касается основного вопроса в этом потоке, другие сообщения дали понять, почему мы не можем привязывать значения к именам столбцов при подготовке операторов, так что вот одно из решений:
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.
Часть меня интересует, если бы вы могли предоставить свою собственную настраиваемую функцию очистки так же просто, как это:
$value = preg_replace('/[^a-zA-Z_]*/', '', $value);
Я действительно не продумал ее, но кажется, что она удаляет все, кроме символов и подчеркиваний возможно работа.