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

Lesezeit: 8 Minuten

Konnen PHP PDO Anweisungen den Tabellen oder Spaltennamen als Parameter akzeptieren
Jrgns

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 sicher meine ich, dass ich das nicht machen möchte

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

Konnen PHP PDO Anweisungen den Tabellen oder Spaltennamen als Parameter akzeptieren
Noah Goodrich

Tabellen- und Spaltennamen können in PDO NICHT durch Parameter ersetzt werden.

In diesem Fall möchten Sie die Daten einfach manuell filtern und bereinigen. Eine Möglichkeit besteht darin, abgekürzte Parameter an die Funktion zu übergeben, die die Abfrage dynamisch ausführt, und dann a zu verwenden switch() -Anweisung zum Erstellen einer weißen Liste gültiger Werte, die für den Tabellen- oder Spaltennamen verwendet werden sollen. Auf diese Weise gehen niemals Benutzereingaben direkt in die Abfrage ein. 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 Werte verwendet werden, die Sie verwenden möchten.

  • +1 für Whitelisting-Optionen, anstatt irgendeine dynamische Methode zu verwenden. Eine andere Alternative könnte darin bestehen, akzeptable Tabellennamen einem Array mit Schlüsseln zuzuordnen, die der potenziellen Benutzereingabe entsprechen (z array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)

    – Kzqai

    22. Dezember 2011 um 18:05 Uhr


  • Beim Durchlesen fällt mir auf, dass das Beispiel hier ungültiges SQL für fehlerhafte Eingaben generiert, weil es keine hat default. Wenn Sie dieses Muster verwenden, sollten Sie entweder eines Ihrer beschriften caseist wie defaultoder fügen Sie einen expliziten Fehlerfall hinzu, z default: throw new InvalidArgumentException;

    – IMSoP

    22. Oktober 2015 um 9:34 Uhr

  • Ich dachte an ein einfaches if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Danke für die Idee.

    – Phil Melodie

    2. März 2016 um 17:20 Uhr


  • ich vermisse mysql_real_escape_string(). Vielleicht kann ich es hier sagen, ohne dass jemand einspringt und sagt: “Aber Sie brauchen es nicht mit PDO”

    – Rolf

    20. Oktober 2018 um 8:39 Uhr

  • Das andere Problem ist, dass dynamische Tabellennamen die SQL-Inspektion unterbrechen.

    – Akira

    20. Juli 2019 um 11:02 Uhr

Konnen PHP PDO Anweisungen den Tabellen oder Spaltennamen als Parameter akzeptieren
IMSoP

Verstehen warum Das Binden eines Tabellen- (oder Spalten-) Namens funktioniert nicht, Sie müssen verstehen, wie die Platzhalter in vorbereiteten Anweisungen funktionieren: Sie werden nicht einfach als (geeignet maskierte) Zeichenfolgen eingesetzt und das resultierende SQL ausgeführt. Stattdessen erstellt ein DBMS, das aufgefordert wird, eine Anweisung “vorzubereiten”, einen vollständigen Abfrageplan, wie es diese Abfrage ausführen würde, einschließlich der zu verwendenden Tabellen und Indizes, die unabhängig davon, wie Sie die Platzhalter ausfüllen, gleich bleiben.

Der Plan für SELECT name FROM my_table WHERE id = :value wird dasselbe sein, was auch immer Sie ersetzen :valueaber die scheinbar ähnlich SELECT name FROM :table WHERE id = :value kann nicht geplant werden, da das DBMS keine Ahnung hat, aus welcher Tabelle Sie tatsächlich auswählen werden.

Dies kann oder sollte eine Abstraktionsbibliothek wie PDO auch nicht umgehen, da dies die beiden Hauptzwecke vorbereiteter Anweisungen zunichte machen würde: 1) der Datenbank zu ermöglichen, im Voraus zu entscheiden, wie eine Abfrage ausgeführt wird, und dieselbe zu verwenden mehrmals planen; und 2) um Sicherheitsprobleme zu verhindern, indem die Logik der Abfrage von der Variableneingabe getrennt wird.

  • Stimmt, berücksichtigt aber nicht die Emulation der PDO-Anweisung (was könnten möglicherweise SQL-Objektbezeichner parametrisieren, obwohl ich immer noch zustimme, dass dies wahrscheinlich nicht der Fall sein sollte).

    – eigelb

    27. Dezember 2013 um 19:40 Uhr

  • @eggyal Ich denke, die Emulation zielt darauf ab, Standardfunktionen für alle DBMS-Varianten zum Laufen zu bringen, anstatt völlig neue Funktionen hinzuzufügen. Ein Platzhalter für Bezeichner würde auch eine eindeutige Syntax benötigen, die von keinem DBMS direkt unterstützt wird. PDO ist ein ziemlich niedriger Wrapper und bietet beispielsweise keine SQL-Generierung für TOP/LIMIT/OFFSET Klauseln, daher wäre dies als Feature etwas fehl am Platz.

    – IMSoP

    1. Januar 2014 um 19:53 Uhr


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 wie …

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

Im Inneren überprüfe ich 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 der PHP-Check vor dem Ausführen von PDO so 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);
}

  • gut für kurze Lösung, aber warum nicht einfach $pdo->query($sql)

    – jscripter

    15. Dezember 2014 um 1:32 Uhr

  • Meistens aus Gewohnheit bei der Vorbereitung von Abfragen, die eine Variable binden müssen. Lesen Sie auch wiederholte Aufrufe sind schneller, wenn Sie hier stackoverflow.com/questions/4700623/pdos-query-vs-execute ausführen

    – Anziehen

    13. April 2015 um 13:33 Uhr

  • In Ihrem Beispiel gibt es keine wiederholten Aufrufe

    – Ihr gesunder Menschenverstand

    21. März 2018 um 7:27 Uhr

Die Verwendung des ersteren ist nicht von Natur aus sicherer als das letztere. Sie müssen die Eingabe bereinigen, unabhängig davon, ob sie Teil eines Parameterarrays oder einer einfachen Variablen ist. Ich sehe also nichts Falsches daran, die letztere Form mit zu verwenden $tablevorausgesetzt, Sie stellen sicher, dass der Inhalt von $table ist sicher (Alphanum plus Unterstriche?), bevor Sie es verwenden.

(Späte Antwort, konsultieren Sie meine Randnotiz).

Die gleiche Regel gilt beim Versuch, eine “Datenbank” zu erstellen.

Sie können eine vorbereitete Anweisung nicht verwenden, um eine Datenbank zu binden.

Dh:

CREATE DATABASE IF NOT EXISTS :database

wird nicht funktionieren. Verwenden Sie stattdessen eine Safelist.

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

1647096248 184 Konnen PHP PDO Anweisungen den Tabellen oder Spaltennamen als Parameter akzeptieren
Phil Lanasa

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

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

Ich habe nicht wirklich darüber nachgedacht, aber es scheint, als ob das Entfernen von allem außer Zeichen und Unterstrichen funktionieren könnte.

1647096249 294 Konnen PHP PDO Anweisungen den Tabellen oder Spaltennamen als Parameter akzeptieren
Mann

Was die Hauptfrage in diesem Thread betrifft, haben die anderen Posts deutlich gemacht, warum wir beim Vorbereiten von Anweisungen keine Werte an Spaltennamen binden können. Hier ist also 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, also ist es unnötig zu sagen, dass Kopieren-> Einfügen nicht funktioniert. An Ihre Bedürfnisse anpassen. Dies bietet möglicherweise keine 100% ige Sicherheit, ermöglicht jedoch eine gewisse Kontrolle über die Spaltennamen, wenn sie als dynamische Zeichenfolgen “hereinkommen” und auf Benutzerseite geändert werden können. Darüber hinaus ist es nicht erforderlich, ein Array mit Ihren Tabellenspaltennamen und -typen zu erstellen, da sie aus dem information_schema extrahiert werden.

994020cookie-checkKönnen PHP-PDO-Anweisungen den Tabellen- oder Spaltennamen als Parameter akzeptieren?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy