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

Почему я не могу передать имя таблицы в подготовленный оператор 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"
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
249
0
84 883
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

Учитывая, что первый вариант не сработает, вам придется использовать какую-либо форму динамического построения запросов.

Noah Goodrich 08.10.2008 15:58

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

Adam Bellaire 08.10.2008 16:01
Ответ принят как подходящий

Имена таблиц и столбцов НЕ МОГУТ быть заменены параметрами в 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') и т. д.).

Kzqai 22.12.2011 22:05

Читая это, мне приходит в голову, что приведенный здесь пример генерирует недопустимый SQL для неправильного ввода, потому что у него нет default. При использовании этого шаблона вы должны либо пометить один из ваших case как default, либо добавить явный случай ошибки, например default: throw new InvalidArgumentException;.

IMSoP 22.10.2015 12:34

Я думал о простом if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Спасибо за идею.

Phil Tune 02.03.2016 20:20

Я скучаю по mysql_real_escape_string(). Может быть, здесь я могу сказать это без того, чтобы кто-то прыгнул и сказал: «Но вам это не нужно с PDO»

Rolf 20.10.2018 11:39

Другая проблема заключается в том, что имена динамических таблиц нарушают проверку SQL.

Acyra 20.07.2019 14:02

Чтобы понять, что привязка Почему к имени таблицы (или столбца) не работает, вы должны понимать, как работают заполнители в подготовленных операторах: они не просто подставляются в строки (с соответствующим экранированием), а результирующий 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 27.12.2013 23:40

@eggyal Я предполагаю, что эмуляция нацелена на то, чтобы стандартная функциональность работала на всех разновидностях СУБД, а не на добавление совершенно новой функциональности. Заполнитель для идентификаторов также потребует отдельного синтаксиса, который напрямую не поддерживается ни одной СУБД. PDO является довольно низкоуровневой оболочкой и, например, не предлагает и генерацию SQL для предложений TOP / LIMIT / OFFSET, так что это было бы немного неуместно в качестве функции.

IMSoP 01.01.2014 23:53

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

jscripter 15.12.2014 04:32

В основном по привычке при подготовке запросов, которые должны связывать переменную. Также чтение повторных вызовов выполняется быстрее с выполнением здесь stackoverflow.com/questions/4700623/pdos-query-vs-execute

Don 13.04.2015 16:33

в вашем примере нет повторных вызовов

Your Common Sense 21.03.2018 10:27

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

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

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

Имена таблиц MySQL могут содержать другие символы. См. dev.mysql.com/doc/refman/5.0/en/identifiers.html

Phil 29.04.2014 05:31

@PhilLaNasa на самом деле немного защищать они должны (нужна ссылка). Поскольку большинство СУБД нечувствительны к регистру, сохраняя имя в виде недифференцированных символов, например: MyLongTableName, его легко читать правильно, но если вы проверите сохраненное имя, это будет (вероятно) MYLONGTABLENAME, который не очень читается, поэтому MY_LONG_TABLE_NAME на самом деле более читабельный.

mloureiro 10.08.2015 20:26

Есть очень веская причина не использовать это как функцию: очень и очень редко следует выбирать имя таблицы на основе произвольного ввода. Вы почти наверняка не хотите, чтобы злоумышленник подставил в Select * From $table «пользователей» или «бронирования». Белый список или строгое соответствие шаблону (например, «имена, начинающиеся с отчета_, за которыми следуют только 1–3 цифры») здесь действительно важны.

IMSoP 21.03.2017 14:55

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

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

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

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

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