¿Pueden las declaraciones PHP DOP aceptar el nombre de la tabla o columna como parámetro?

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

  •  06-07-2019
  •  | 
  •  

Pregunta

¿Por qué no puedo pasar el nombre de la tabla a una declaración de DOP preparada?

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

¿Hay otra forma segura de insertar un nombre de tabla en una consulta SQL? Con seguro, quiero decir que no quiero hacer

$sql = "SELECT * FROM $table WHERE 1"
¿Fue útil?

Solución

Los nombres de tabla y columna NO se pueden reemplazar por parámetros en PDO.

En ese caso, simplemente querrá filtrar y sanear los datos manualmente. Una forma de hacer esto es pasar parámetros de taquigrafía a la función que ejecutará la consulta dinámicamente y luego usar una instrucción switch () para crear una lista blanca de valores válidos que se usarán para el nombre de la tabla o nombre de columna. De esa manera, ninguna entrada del usuario va directamente a la consulta. Así por ejemplo:

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

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

Al no dejar un caso predeterminado o al utilizar un caso predeterminado que devuelve un mensaje de error, se asegura de que solo se utilicen los valores que desea utilizar.

Otros consejos

Para entender por qué el enlace de un nombre de tabla (o columna) no funciona, debe comprender cómo funcionan los marcadores de posición en las declaraciones preparadas: no se sustituyen simplemente en cadenas (escapadas adecuadamente) , y el resultado SQL ejecutado. En su lugar, un DBMS pidió que " prepare " una declaración presenta un plan de consulta completo sobre cómo ejecutaría esa consulta, incluidas las tablas e índices que usaría, que serán iguales independientemente de cómo rellene los marcadores de posición.

El plan para SELECCIONAR nombre DE mi tabla WHERE id =: value será el mismo que sustituyas por : valor , pero el SELECT name similar al parecer similar : la tabla WHERE id =: value no se puede planificar, porque el DBMS no tiene idea de qué tabla realmente va a seleccionar.

Esto no es algo que una biblioteca de abstracción como PDO pueda o deba solucionar, ya que anularía los dos propósitos clave de las declaraciones preparadas: 1) para permitir que la base de datos decida por adelantado cómo se ejecutará una consulta, y usa el mismo plan varias veces; y 2) para evitar problemas de seguridad separando la lógica de la consulta de la entrada variable.

Veo que este es un post antiguo, pero lo encontré útil y pensé que compartiría una solución similar a lo que sugirió @kzqai:

Tengo una función que recibe dos parámetros como ...

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

En el interior, compruebo contra las matrices que he configurado para garantizar que solo las tablas y columnas tengan " bendecido " las mesas son accesibles:

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

Luego, la comprobación de PHP antes de ejecutar PDO parece ...

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

El uso del primero no es intrínsecamente más seguro que el segundo, es necesario desinfectar la entrada, ya sea parte de una matriz de parámetros o una variable simple. Por lo tanto, no veo nada incorrecto en el uso de este último formulario con $ table , siempre que se asegure de que el contenido de $ table sea seguro (¿alfanum más guiones bajos?) Antes de usándolo.

(Respuesta tardía, consulte mi nota al margen).

La misma regla se aplica cuando se intenta crear una " base de datos " ;.

No puede usar una declaración preparada para vincular una base de datos.

I.e .:

CREATE DATABASE IF NOT EXISTS :database

no funcionará. Utilice una lista segura en su lugar.

Nota al margen: agregué esta respuesta (como wiki de la comunidad) porque a menudo solía cerrar preguntas, donde algunas personas publicaban preguntas similares a esta al intentar vincular una base de datos y no una tabla y / o columna.

Una parte de mí se pregunta si podría proporcionar su propia función de desinfección personalizada tan simple como esto:

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

Realmente no lo he pensado, pero parece que eliminar cualquier cosa, excepto los caracteres y los guiones bajos, podría funcionar.

En cuanto a la pregunta principal de este hilo, las otras publicaciones dejaron en claro por qué no podemos vincular los valores a los nombres de columnas al preparar declaraciones, así que aquí hay una solución:

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

Lo anterior es solo un ejemplo, por lo que no hace falta decir que copiar y pegar no funcionará. Ajuste según sus necesidades. Es posible que esto no proporcione un 100% de seguridad, pero permite cierto control sobre los nombres de las columnas cuando " entran " Como cadenas dinámicas y pueden ser cambiadas en los usuarios finales. Además, no es necesario crear una matriz con los nombres y tipos de columnas de su tabla, ya que se extraen del esquema de información.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top