it-swarm.com.de

Können PHP PDO-Anweisungen den Tabellen- oder Spaltennamen als Parameter akzeptieren?

Warum kann ich den Tabellennamen nicht an eine vorbereitete PDO-Anweisung übergeben?

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

Gibt es eine andere sichere Möglichkeit, einen Tabellennamen in eine SQL-Abfrage einzufügen? Mit Safe meine ich, dass ich es nicht tun will

$sql = "SELECT * FROM $table WHERE 1"
228
Jrgns

Tabellen- und Spaltennamen KÖNNEN NICHT durch Parameter in PDO ersetzt werden.

In diesem Fall müssen Sie die Daten lediglich manuell filtern und bereinigen. Eine Möglichkeit, dies zu tun, besteht darin, der Funktion, die die Abfrage dynamisch ausführt, Kurzzeichenparameter zu übergeben und anschließend mit einer switch() -Anweisung eine Positivliste der gültigen Werte zu erstellen, die für den Tabellennamen oder den Spaltennamen verwendet werden sollen . Auf diese Weise wird keine Benutzereingabe direkt in die Abfrage eingegeben. Also zum Beispiel:

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

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

Indem Sie keinen Standardfall belassen oder einen Standardfall verwenden, der eine Fehlermeldung zurückgibt, stellen Sie sicher, dass nur die Werte verwendet werden, die Sie verwenden möchten.

205
Noah Goodrich

Um zu verstehen, dass warum das Binden eines Tabellennamens (oder eines Spaltennamens) nicht funktioniert, müssen Sie verstehen, wie die Platzhalter in vorbereiteten Anweisungen funktionieren: Sie werden nicht einfach durch (entsprechend maskierte) Zeichenfolgen ersetzt resultierendes SQL ausgeführt. Stattdessen erstellt ein DBMS, der zum "Vorbereiten" einer Anweisung aufgefordert wird, einen vollständigen Abfrageplan für die Ausführung dieser Abfrage, einschließlich der verwendeten Tabellen und Indizes, die unabhängig von der Eingabe der Platzhalter identisch sind.

Der Plan für SELECT name FROM my_table WHERE id = :value Ist derselbe, was auch immer Sie für :value Einsetzen, aber der scheinbar ähnliche SELECT name FROM :table WHERE id = :value Kann nicht geplant werden, da das DBMS keine Ahnung hat, welche Tabelle Sie tatsächlich haben werde auswählen aus.

Dies kann oder sollte eine Abstraktionsbibliothek wie PDO auch nicht umgehen, da sie die beiden Hauptziele vorbereiteter Anweisungen zunichte macht: 1) Die Datenbank kann im Voraus entscheiden, wie eine Abfrage ausgeführt wird, und dieselbe verwenden mehrmals planen; und 2) um Sicherheitsprobleme zu vermeiden, indem die Logik der Abfrage von der Variableneingabe getrennt wird.

133
IMSoP

Ich sehe, dass dies ein alter Beitrag ist, aber ich fand ihn nützlich und dachte, ich würde eine Lösung teilen, die der von @kzqai vorgeschlagenen ähnelt:

Ich habe eine Funktion, die zwei Parameter empfängt wie ...

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

Im Inneren überprüfe ich die Arrays, die ich eingerichtet habe, um sicherzustellen, dass nur Tabellen und Spalten mit "gesegneten" Tabellen zugänglich sind:

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

Dann sieht das PHP vor dem Ausführen von PDO wie folgt aus ...

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

Erstere zu verwenden, ist von Natur aus nicht sicherer als letztere. Sie müssen die Eingabe bereinigen, unabhängig davon, ob sie Teil eines Parameterarrays oder einer einfachen Variablen ist. Daher sehe ich nichts falsches daran, das letztere Formular mit $table Zu verwenden, vorausgesetzt, Sie stellen sicher, dass der Inhalt von $table Sicher ist (Alphanum plus Unterstriche?), Bevor Sie es verwenden.

4
Adam Bellaire

(Späte Antwort, siehe meine Randnotiz)

Die gleiche Regel gilt, wenn versucht wird, eine "Datenbank" zu erstellen.

Sie können keine vorbereitete Anweisung zum Binden einer Datenbank verwenden.

Dh:

CREATE DATABASE IF NOT EXISTS :database

wird nicht funktionieren. Verwenden Sie stattdessen eine Listen sicherer Adressen.

Randnotiz: Ich habe diese Antwort (als Community-Wiki) hinzugefügt, da sie häufig zum Schließen von Fragen verwendet wurde, bei denen einige Leute ähnliche Fragen stellten, um zu versuchen, eine Datenbank zu binden und keine Tabelle und/oder Spalte.

2

Ein Teil von mir fragt sich, ob Sie Ihre eigene benutzerdefinierte Desinfektionsfunktion so einfach bereitstellen könnten:

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

Ich habe nicht wirklich darüber nachgedacht, aber es scheint, als würde alles außer Zeichen und Unterstrichen funktionieren.

0
Phil LaNasa

Was die Hauptfrage in diesem Thread angeht, haben die anderen Beiträge deutlich gemacht, warum wir beim Vorbereiten von Anweisungen keine Werte an Spaltennamen binden können. Hier ist eine Lösung:

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

Das Obige ist nur ein Beispiel, daher funktioniert Kopieren-> Einfügen natürlich nicht. Passen Sie sich Ihren Bedürfnissen an. Dies bietet möglicherweise keine 100% ige Sicherheit, ermöglicht jedoch eine gewisse Kontrolle über die Spaltennamen, wenn diese als dynamische Zeichenfolgen "eingehen" und vom Benutzer geändert werden können. Darüber hinaus ist es nicht erforderlich, ein Array mit den Namen und Typen Ihrer Tabellenspalten zu erstellen, da diese aus dem information_schema extrahiert werden.

0
man