Le istruzioni PHP DOP possono accettare il nome di tabella o colonna come parametro?

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

  •  06-07-2019
  •  | 
  •  

Domanda

Perché non riesco a passare il nome della tabella a un'istruzione PDO preparata?

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

Esiste un altro modo sicuro per inserire un nome di tabella in una query SQL? Con sicuro, intendo che non voglio fare

$sql = "SELECT * FROM $table WHERE 1"
È stato utile?

Soluzione

I nomi di tabella e colonna NON POSSONO essere sostituiti da parametri in DOP.

In tal caso, dovrai semplicemente filtrare e disinfettare i dati manualmente. Un modo per farlo è passare parametri di stenografia alla funzione che eseguirà la query in modo dinamico e quindi utilizzare un'istruzione switch () per creare una lista bianca di valori validi da utilizzare per il nome della tabella o il nome della colonna. In questo modo nessun input dell'utente viene mai inserito direttamente nella query. Quindi ad esempio:

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

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

Non lasciando alcun caso predefinito o utilizzando un caso predefinito che restituisce un messaggio di errore, si garantisce che vengano utilizzati solo i valori che si desidera utilizzare.

Altri suggerimenti

Per capire perché l'associazione di un nome di tabella (o colonna) non funziona, devi capire come funzionano i segnaposto nelle istruzioni preparate: non vengono semplicemente sostituiti come stringhe (opportunamente sfuggite) e l'SQL risultante eseguito. Invece, un DBMS ha chiesto di "preparare" un'istruzione fornisce un piano di query completo su come eseguirà quella query, incluse le tabelle e gli indici che utilizzerà, che sarà lo stesso indipendentemente da come si riempiono i segnaposto.

Il piano per SELEZIONA il nome DA my_table DOVE id =: valore sarà lo stesso qualunque cosa sostituisca per : valore , ma il SELEZIONA apparentemente simile DA : table WHERE id =: value non può essere pianificato, in quanto il DBMS non ha idea di quale tabella verrà effettivamente selezionata.

Questo non è qualcosa che una libreria di astrazione come PDO può o dovrebbe aggirare, dal momento che annullerebbe i 2 scopi chiave delle istruzioni preparate: 1) per consentire al database di decidere in anticipo come verrà eseguita una query, e utilizzare lo stesso piano più volte; e 2) per prevenire problemi di sicurezza separando la logica della query dall'input variabile.

Vedo che questo è un vecchio post, ma l'ho trovato utile e ho pensato di condividere una soluzione simile a quanto suggerito da @kzqai:

Ho una funzione che riceve due parametri come ...

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

All'interno controllo gli array che ho impostato per accertarmi che solo le tabelle e le colonne con "benedetto" le tabelle sono accessibili:

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

Quindi il controllo PHP prima di eseguire DOP sembra ...

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'uso del primo non è intrinsecamente più sicuro del secondo, è necessario disinfettare l'input che faccia parte di un array di parametri o di una semplice variabile. Quindi non vedo nulla di sbagliato nell'uso di quest'ultimo modulo con $ table , purché tu ti assicuri che il contenuto di $ table sia sicuro (alfanum plus underscore?) Prima usandolo.

(risposta tardiva, consultare la mia nota a margine).

La stessa regola si applica quando si tenta di creare un " database " ;.

Non è possibile utilizzare un'istruzione preparata per associare un database.

cioè:.

CREATE DATABASE IF NOT EXISTS :database

non funzionerà. Utilizzare invece un elenco di indirizzi attendibili.

Nota a margine: ho aggiunto questa risposta (come wiki della comunità) perché spesso veniva utilizzata per chiudere le domande, in cui alcune persone hanno pubblicato domande simili a queste nel tentativo di associare un database e non una tabella e / o una colonna.

Parte di me si chiede se potresti fornire la tua funzione di sanificazione personalizzata semplice come questa:

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

Non ci ho davvero pensato, ma sembra che rimuovere qualsiasi cosa tranne caratteri e caratteri di sottolineatura potrebbe funzionare.

Per quanto riguarda la domanda principale in questo thread, gli altri post hanno chiarito perché non possiamo associare i valori ai nomi delle colonne durante la preparazione delle istruzioni, quindi ecco una soluzione:

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

Quanto sopra è solo un esempio, quindi inutile dirlo, copy- > paste non funzionerà. Adatta alle tue esigenze. Ora questo potrebbe non fornire una sicurezza del 100%, ma consente un certo controllo sui nomi delle colonne quando "entrano". come stringhe dinamiche e possono essere modificate sul lato utente. Inoltre, non è necessario creare alcun array con i nomi e i tipi delle colonne della tabella poiché sono estratti da information_schema.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top