Les instructions PHP PDO peuvent-elles accepter le nom de la table ou de la colonne en tant que paramètre?

StackOverflow https://stackoverflow.com/questions/182287

  •  06-07-2019
  •  | 
  •  

Question

Pourquoi ne puis-je pas transmettre le nom de la table à une instruction PDO préparée?

$stmt = $dbh->prepare('SELECT * FROM :table WHERE 1');
if ($stmt->execute(array(':table' => 'users'))) {
    var_dump($stmt->fetchAll());
}

Existe-t-il un autre moyen sûr d’insérer un nom de table dans une requête SQL? Avec safe, je veux dire que je ne veux pas faire

$sql = "SELECT * FROM $table WHERE 1"
Était-ce utile?

La solution

Les noms de table et de colonne NE PEUVENT PAS être remplacés par des paramètres dans PDO.

Dans ce cas, vous voudrez simplement filtrer et assainir les données manuellement. Une façon de faire est de passer des paramètres abrégés à la fonction qui exécutera la requête dynamiquement, puis utilisera une instruction switch () pour créer une liste blanche de valeurs valides à utiliser pour le nom de la table. ou nom de colonne. De cette manière, aucune entrée d’utilisateur ne va directement dans la requête. Ainsi, par exemple:

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

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

En ne laissant aucun cas par défaut ou en utilisant un cas par défaut renvoyant un message d'erreur, vous vous assurez que seules les valeurs que vous souhaitez utiliser sont utilisées.

Autres conseils

Pour comprendre pourquoi la liaison d'un nom de table (ou de colonne) ne fonctionne pas, vous devez comprendre le fonctionnement des espaces réservés dans les instructions préparées: ils ne sont pas simplement substitués par des chaînes (échappées appropriées). et le résultat SQL exécuté. Au lieu de cela, un SGBD a demandé de "préparer" une instruction fournit un plan de requête complet indiquant la manière dont elle exécuterait cette requête, y compris les tables et les index qu’elle utiliserait, qui sera identique quelle que soit la manière dont vous remplissez les espaces réservés.

Le plan pour SELECT nom FROM FROM ma_table WHERE id =: valeur sera le même quel que soit le substitut de : valeur , mais l'apparence similaire SELECT nom FROM : table WHERE id =: valeur ne peut pas être planifié, car le SGBD n'a aucune idée de la table dans laquelle vous allez réellement sélectionner.

Ce n'est pas quelque chose qu'une bibliothèque d'abstraction comme PDO peut ou devrait contourner, car elle irait à l'encontre des deux objectifs clés des instructions préparées: 1) permettre à la base de données de décider à l'avance du mode d'exécution d'une requête, et utiliser le même plan plusieurs fois; et 2) éviter les problèmes de sécurité en séparant la logique de la requête de l'entrée de variable.

Je vois qu'il s'agit d'un ancien message, mais je l'ai trouvé utile et j'ai pensé partager une solution similaire à celle suggérée par @kzqai:

J'ai une fonction qui reçoit deux paramètres tels que ...

function getTableInfo($inTableName, $inColumnName) {
    ....
}

À l'intérieur, je vérifie les tableaux que j'ai configurés pour m'assurer que seuls les tableaux et les colonnes avec "bienheureux" les tables sont accessibles:

$allowed_tables_array = array('tblTheTable');
$allowed_columns_array['tblTheTable'] = array('the_col_to_check');

Ensuite, la vérification PHP avant d'exécuter PDO ressemble à ...

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);
}

L'utilisation de la première n'est pas intrinsèquement plus sûre que la dernière, vous devez purifier l'entrée, qu'elle fasse partie d'un tableau de paramètres ou d'une simple variable. Donc, je ne vois rien de mal à utiliser ce dernier formulaire avec $ table , à condition de vous assurer que le contenu de $ table est sûr (alphanum plus underscores?) Avant l'utiliser.

(réponse tardive, consultez ma note d'accompagnement).

La même règle s'applique lorsque vous essayez de créer une "base de données".

Vous ne pouvez pas utiliser une instruction préparée pour lier une base de données.

I.e.:

CREATE DATABASE IF NOT EXISTS :database

ne fonctionnera pas. Utilisez plutôt une liste de sécurité.

Remarque latérale: j'ai ajouté cette réponse (en tant que wiki de communauté), car il était souvent utilisé pour fermer des questions avec des personnes qui posaient des questions similaires à celles-ci en essayant de lier une base de données et non un tableau et / ou une colonne.

Une partie de moi se demande si vous pourriez fournir votre propre fonction de désinfection personnalisée aussi simple que cela:

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

Je n'y ai pas vraiment réfléchi, mais il semble que supprimer quoi que ce soit, à l'exception des caractères et des traits de soulignement, pourrait fonctionner.

En ce qui concerne la question principale de ce fil de discussion, les autres publications expliquent pourquoi nous ne pouvons pas associer des valeurs aux noms de colonnes lors de la préparation des instructions. Voici donc une solution:

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));

Ce qui précède n’est qu’un exemple. Inutile de dire que copier-coller ne fonctionnera pas. Ajustez à vos besoins. Maintenant, cela ne garantit peut-être pas une sécurité à 100%, mais cela permet un certain contrôle sur les noms de colonnes quand ils "entrent" dans. en tant que chaînes dynamiques et peuvent être modifiés du côté des utilisateurs. De plus, il n’est pas nécessaire de construire un tableau avec les noms et les types de colonnes de votre table car ils sont extraits de la classe information_schema.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top