Le istruzioni PHP DOP possono accettare il nome di tabella o colonna come parametro?
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"
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.