هل يمكن لبيانات PHP PDO قبول اسم الجدول أو العمود كمعلمة؟

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

  •  06-07-2019
  •  | 
  •  

سؤال

لماذا لا يمكنني تمرير اسم الجدول إلى بيان PDO مُجهز؟

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

هل هناك طريقة آمنة أخرى لإدراج اسم جدول في استعلام SQL؟مع آمنة، أعني أنني لا أريد أن أفعل

$sql = "SELECT * FROM $table WHERE 1"
هل كانت مفيدة؟

المحلول

لا يمكن استبدال أسماء الجداول والأعمدة بمعلمات في PDO.

في هذه الحالة، ستحتاج ببساطة إلى تصفية البيانات وتعقيمها يدويًا.إحدى الطرق للقيام بذلك هي تمرير معلمات مختصرة إلى الوظيفة التي ستقوم بتنفيذ الاستعلام ديناميكيًا ثم استخدام ملف switch() عبارة لإنشاء قائمة بيضاء بالقيم الصالحة لاستخدامها في اسم الجدول أو اسم العمود.بهذه الطريقة لن يتم إدخال أي إدخال من المستخدم مباشرة في الاستعلام.لذلك على سبيل المثال:

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

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

من خلال عدم ترك أي حالة افتراضية أو استخدام حالة افتراضية ترجع رسالة خطأ، فإنك تضمن استخدام القيم التي تريد استخدامها فقط.

نصائح أخرى

لفهم <م> لماذا ملزمة طاولة (أو عمود) اسم لا تعمل، عليك أن تفهم كيفية عمل النائبة في البيانات المعدة: لا يتم استبدال ببساطة الدخول ك (هرب مناسب) السلاسل وSQL الناتجة تنفيذها. بدلا من ذلك، طلب من DBMS إلى "إعداد" بيان يأتي مع خطة الاستعلام كاملة لكيف يمكن ان تنفيذ هذا الاستعلام، بما في ذلك التي الجداول والفهارس انها ستستخدم، والتي سوف تكون هي نفسها بغض النظر عن كيفية ملء النائبة.

وخطة لSELECT name FROM my_table WHERE id = :value سوف تكون هي نفسها مهما كنت بديلا عن :value، ولكن لا يمكن التخطيط لSELECT name FROM :table WHERE id = :value مماثلة على ما يبدو، لأن نظم إدارة قواعد البيانات لديها أي فكرة عما الجدول وأنت تسير في الواقع إلى اختيار من بينها.

وهذا ليس شيئا يمكن للمكتبة التجريد مثل شركة تنمية نفط عمان أو يجب أن تعمل حولها، وإما لأنها من شأنها أن تحبط أغراض رئيسية 2 من البيانات المعدة: 1) للسماح للقاعدة أن تقرر مسبقا كيف سيتم تشغيل الاستعلام، و استخدام نفس الخطة عدة مرات. و 2) لمنع القضايا الأمنية التي تفصل بين منطق الاستعلام من المدخلات متغير.

وأرى أن هذا هو آخر العمر، ولكنني وجدت أنه من المفيد واعتقدت أن تشترك في حل مماثل لماkzqai اقترح:

ولدي وظيفة أن يتلقى معلمتين مثل ...

function getTableInfo($inTableName, $inColumnName) {
    ....
}
داخل I تحقق ضد صفائف لقد حدد

وحتى تأكد من الجداول والأعمدة فقط مع "المباركة" الجداول يمكن الوصول إليها:

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

وثم الاختيار PHP قبل تشغيل PDO يشبه ...

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

وباستخدام السابق هو ليس بطبيعتها أكثر أمانا من هذا الأخير، تحتاج إلى تطهير المدخلات سواء كان ذلك جزءا من مجموعة المعلمة أو متغير بسيط. لذلك أنا لا أرى بأسا باستخدام النموذج الأخير مع $table، شريطة التأكد من أن محتوى $table آمن (alphanum بالإضافة إلى سفلية؟) قبل استخدامه.

(إجابة متأخرة، راجع ملاحظتي الجانبية).

تنطبق نفس القاعدة عند محاولة إنشاء "قاعدة بيانات".

لا يمكنك استخدام عبارة معدة لربط قاعدة بيانات.

أي.:

CREATE DATABASE IF NOT EXISTS :database

لن يعمل.استخدم القائمة الآمنة بدلاً من ذلك.

ملاحظة جانبية: لقد أضفت هذه الإجابة (كموقع ويكي مجتمعي) لأنها تستخدم غالبًا لإغلاق الأسئلة، حيث نشر بعض الأشخاص أسئلة مشابهة لهذه في محاولة ربط قاعدة البيانات وليس جدول و/أو عمود.

وجزء مني يتساءل إذا كنت يمكن أن توفر الخاصة ظيفة التعقيم المخصصة بسيطة مثل هذا:

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

وأنا لم أفكر حقا من خلال ذلك، ولكن يبدو أن إزالة أي شيء ما عدا الشخصيات وسفلية قد عمل.

وأما عن السؤال الرئيسي في هذا الموضوع، قدمت وظائف أخرى من الواضح لماذا لا يمكننا ربط القيم إلى أسماء الأعمدة عند إعداد البيانات، حتى هنا هو حل واحد:

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

ما سبق هو مجرد مثال، وغني جدا أن أقول، copy-> لصق لن تعمل. ضبط لاحتياجاتك. الآن هذا قد لا توفر الأمن 100٪، لكنه يسمح لبعض السيطرة على أسماء الأعمدة عندما "تأتي في" السلاسل وديناميكية ويمكن تغييره على المستخدمين النهائيين. وعلاوة على ذلك، ليست هناك حاجة لبناء بعض مجموعة مع أسماء الأعمدة الجدول الخاص بك وأنواع لأنها تستخرج من information_schema.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top