পিএইচপি পিডিও বিবৃতি টেবিল বা কলামের নামটিকে প্যারামিটার হিসাবে গ্রহণ করতে পারে?


243

কেন আমি প্রস্তুত পিডিও বিবৃতিতে টেবিলের নামটি পাস করতে পারি না?

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

এসকিউএল কোয়েরিতে কোনও টেবিলের নাম সন্নিবেশ করার অন্য কোনও নিরাপদ উপায় আছে? নিরাপদ সহ, আমি বোঝাতে চাইছি না

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

উত্তর:


212

টেবিল এবং কলামের নামগুলি PDO এ পরামিতিগুলি দ্বারা প্রতিস্থাপিত করা যাবে না।

সেক্ষেত্রে আপনি কেবল ম্যানুয়ালি ডেটা ফিল্টার এবং স্যানিটাইজ করতে চাইবেন। এটি করার একটি উপায় শর্টহ্যান্ড প্যারামিটারগুলিতে ফাংশনটি পাস করা যা কোয়েরিটি গতিশীলভাবে কার্যকর করবে এবং তারপরে switch()টেবিলের নাম বা কলামের নামের জন্য বৈধ মানগুলির একটি সাদা তালিকা তৈরি করতে একটি বিবৃতি ব্যবহার করবে। এইভাবে কোনও ব্যবহারকারীর ইনপুট কখনই সরাসরি ক্যোয়ারিতে যায় না। উদাহরণস্বরূপ:

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

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

কোনও ডিফল্ট কেস না রেখে বা কোনও ত্রুটি বার্তা ফেরত এমন কোনও ডিফল্ট কেস ব্যবহার করে আপনি নিশ্চিত হন যে আপনি যে মানগুলি ব্যবহার করতে চান তা ব্যবহার হয়ে যায় get


17
কোনও ধরণের গতিশীল পদ্ধতি ব্যবহার না করে হোয়াইটলিস্টিং বিকল্পগুলির জন্য +1। অন্য বিকল্প হতে পারে সম্ভাব্য ব্যবহারকারীর ইনপুট (উদাহরণস্বরূপ array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data')) এর সাথে সম্পর্কিত এমন
অ্যারেগুলিতে

4
এটি পড়ার পরে, আমার কাছে এটি ঘটে যে এখানে উদাহরণটি খারাপ ইনপুটটির জন্য অবৈধ এসকিউএল উত্পন্ন করে, কারণ এর কোনও নেই default। যদি এই প্যাটার্নটি ব্যবহার করা হয় তবে আপনার নিজের casedefaultdefault: throw new InvalidArgumentException;
কোনওটিকে

3
আমি সরল ভাবছিলাম if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }। ধারণার জন্য ধন্যবাদ।
ফিল টুন

2
আমি মিস্ mysql_real_escape_string()। কেউ এখানে ঝাঁপিয়ে পড়ে এবং "PDO দিয়ে আপনার দরকার নেই" না বলে আমি এখানে এটি বলতে পারি
রল্ফ

অন্য সমস্যাটি হ'ল গতিশীল টেবিলের নামগুলি এসকিউএল পরিদর্শন ভঙ্গ করে।
আকায়রা

143

বোঝার জন্য কেন একটি টেবিল (অথবা কলাম) নাম বাঁধাই কাজ না করে, আপনি বুঝতে কিভাবে প্রস্তুত বিবৃতি কাজে প্লেসহোল্ডার আছে: তারা কেবলমাত্র হিসাবে (উপযুক্ত পলান) স্ট্রিং প্রতিস্থাপিত করা হয় না, এবং তার ফলে এসকিউএল মৃত্যুদন্ড কার্যকর। পরিবর্তে, একটি ডিবিএমএস "প্রস্তুত" করতে একটি বিবৃতি আসে যাতে এটি কীভাবে সারণি এবং সূচকগুলি ব্যবহার করবে, কীভাবে আপনি সেই স্থানটি পূরণ করবেন তা নির্ধারণ না করে কীভাবে সেই ক্যোয়ারীটি কার্যকর করবে তার সম্পূর্ণ প্রশ্নের পরিকল্পনা নিয়ে আসে।

SELECT name FROM my_table WHERE id = :valueআপনার বিকল্পের জন্য যা পরিকল্পনা করা হবে একই রকম হবে :value, তবে আপাতদৃষ্টিতে অনুরূপ SELECT name FROM :table WHERE id = :valueপরিকল্পনা করা যায় না, কারণ আপনি আসলে কোন টেবিলটি বেছে নিতে চলেছেন তা ডিবিএমএসের কোনও ধারণা নেই।

এটি পিডিওর মতো কোনও বিমূর্ত গ্রন্থাগার নয় বা এর চারপাশে কাজ করা উচিত, যেহেতু এটি প্রস্তুত বিবৃতিগুলির 2 মূল উদ্দেশ্যগুলিকে পরাস্ত করবে: 1) কীভাবে কোনও ক্যুরি চালানো হবে তা ডেটাবেসকে আগে থেকেই সিদ্ধান্ত নিতে এবং একই ব্যবহার করতে একাধিকবার পরিকল্পনা; এবং 2) ভেরিয়েবল ইনপুট থেকে কোয়েরির যুক্তি আলাদা করে সুরক্ষা সমস্যাগুলি রোধ করতে।


1
সত্য, তবে PDO এর প্রস্তুত বিবৃতি অনুকরণের জন্য অ্যাকাউন্ট করে না (যা সম্ভবত এসকিউএল অবজেক্ট শনাক্তকারীদের প্যারামিটারাইজ করতে পারে, যদিও আমি এখনও সম্মত হই যে এটি সম্ভবত না হওয়া উচিত)।
উদ্বিগ্ন

1
@ ইগিজিয়াল আমার ধারণা অনুকরণটি সম্পূর্ণ নতুন কার্যকারিতা যুক্ত করার পরিবর্তে সমস্ত ডিবিএমএস স্বাদে মানক কার্যকারিতা তৈরি করার লক্ষ্য। সনাক্তকারীদের জন্য একটি স্থানধারকের জন্য পৃথক বাক্য গঠন প্রয়োজন যা কোনও ডিবিএমএস সরাসরি সমর্থন করে না। পিডিও হ'ল একটি নিম্ন স্তরের মোড়ক, এবং উদাহরণস্বরূপ অফার এবং TOP/ LIMIT/ OFFSETক্লজগুলির জন্য এসকিউএল জেনারেশনের জন্য নয় , তাই এটি বৈশিষ্ট্য হিসাবে কিছুটা জায়গাছাড়া হবে।
আইএমএসওপি

13

আমি দেখতে এটি একটি পুরানো পোস্ট, কিন্তু আমি এটি দরকারী মনে হয়েছিল এবং আমি ভেবেছিলাম @kzqai পরামর্শ মত একটি সমাধান শেয়ার করব:

আমার একটি ফাংশন রয়েছে যা দুটি পরামিতি যেমন ...

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

"বরকতময়" টেবিলযুক্ত কেবল সারণী এবং কলামগুলি অ্যাক্সেসযোগ্য কিনা তা নিশ্চিত করার জন্য আমি অ্যারেগুলির বিরুদ্ধে যাচাই করি:

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

তারপরে পিডিও চালানোর আগে পিএইচপি চেক দেখে মনে হচ্ছে ...

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

2
সংক্ষিপ্ত সমাধানের জন্য ভাল, তবে কেবল কেন নয়$pdo->query($sql)
jscriPoint

বেশিরভাগ অভ্যাসের বাইরে ক্যোয়ারী প্রস্তুত করার সময় যে কোনও ভেরিয়েবলকে বাঁধতে হয়। এছাড়াও পুনরাবৃত্তি কলগুলি দ্রুত পড়ুন / এক্সিকিউট করুন এখানে স্ট্যাকওভারফ্লো
ডন

আপনার উদাহরণটিতে পুনরাবৃত্তি কলগুলি নেই
আপনার সাধারণ জ্ঞান

4

পূর্ববর্তীটি ব্যবহার করা শেষের চেয়ে সহজাতভাবে বেশি নিরাপদ নয়, আপনাকে ইনপুটটি প্যারামিটার অ্যারের অংশ বা একটি সহজ ভেরিয়েবলের অংশ হতে পারে না it সুতরাং আমি পরের ফর্মটি ব্যবহার করে কোনও ভুল দেখতে পাচ্ছি না $table, তবে আপনি যদি নিশ্চিত হন যে $tableব্যবহারের আগে সামগ্রীটি নিরাপদ (আলফানাম প্লাস আন্ডারস্কোর?) আছে কিনা।


প্রথম বিকল্পটি কাজ করবে না তা বিবেচনা করে আপনাকে গতিশীল কোয়েরি বিল্ডিংয়ের কিছু ফর্ম ব্যবহার করতে হবে।
নোয়া গুডরিচ

হ্যাঁ, উল্লিখিত প্রশ্ন এটি কাজ করবে না। আমি কেন এটি বর্ণনা করার চেষ্টা করছিলাম এমনকি কেন সেভাবে করার চেষ্টা করা ভয়ানক গুরুত্বপূর্ণ ছিল না।
অ্যাডাম বেলায়ার

3

(দেরিতে উত্তর, আমার পার্শ্ব নোট সাথে পরামর্শ করুন)।

"ডাটাবেস" তৈরি করার চেষ্টা করার সময় একই নিয়ম প্রযোজ্য।

আপনি একটি ডাটাবেস বাঁধতে প্রস্তুত বিবৃতি ব্যবহার করতে পারবেন না।

অর্থাৎ,

CREATE DATABASE IF NOT EXISTS :database

কাজ করবে না. পরিবর্তে একটি নিরাপদ তালিকা ব্যবহার করুন।

পার্শ্ব দ্রষ্টব্য: আমি এই উত্তরটি (একটি সম্প্রদায়ের উইকি হিসাবে) যুক্ত করেছি কারণ এটি প্রায়শই প্রশ্নগুলি বন্ধ করে দিত যেখানে কিছু লোক কোনও টেবিল এবং / বা কলাম না করে একটি ডেটাবেস বাঁধতে চেষ্টা করার ক্ষেত্রে এর অনুরূপ প্রশ্ন পোস্ট করেছিল ।


0

আমার একটি অংশ অবাক করে দেয় আপনি যদি নিজের কাস্টম স্যানিটাইজিং ফাংশনটি এত সহজ সরবরাহ করতে পারেন:

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

আমি এর মাধ্যমে সত্যই ভাবিনি, তবে মনে হচ্ছে অক্ষর এবং আন্ডারস্কোর ব্যতীত অন্য কিছু সরিয়ে ফেলার মতো কাজ হতে পারে।


1
মাইএসকিউএল টেবিলের নামগুলিতে অন্যান্য অক্ষর থাকতে পারে। দেখুন dev.mysql.com/doc/refman/5.0/en/identifiers.html
ফিল

@ ফিললানাসা আসলে কিছু তাদের রক্ষা করতে পারে (প্রয়োজনের রেফারেন্স)। যেহেতু বেশিরভাগ ডিবিএমএস হ'ল নামটি কোনও অ-বিভেদযুক্ত অক্ষরে সংরক্ষণ করে MyLongTableName, উদাহরণস্বরূপ : এটি সঠিকভাবে পড়া সহজ, তবে আপনি যদি সঞ্চিত নামটি পরীক্ষা করেন তবে এটি সম্ভবত (সম্ভবত) MYLONGTABLENAMEযা খুব পঠনযোগ্য নয়, তাই MY_LONG_TABLE_NAMEপ্রকৃতপক্ষে আরও পঠনযোগ্য।
mloureiro

এটি একটি ফাংশন হিসাবে না রাখার খুব ভাল কারণ রয়েছে: আপনি খুব কমই স্বেচ্ছাসেবী ইনপুট ভিত্তিক কোনও সারণীর নাম নির্বাচন করা উচিত select আপনি অবশ্যই কোনও দূষিত ব্যবহারকারীকে "ব্যবহারকারী" বা "বুকিং" এর বিকল্প হিসাবে বেছে নিতে চান না Select * From $table। একটি শ্বেতলিস্ট বা কঠোর প্যাটার্ন ম্যাচ (উদাহরণস্বরূপ "নামগুলির সূচনা রিপোর্ট_ কেবল 1 থেকে 3 অঙ্কের পরে") এখানে সত্যই প্রয়োজনীয়।
আইএমএসওপি

0

এই থ্রেডের মূল প্রশ্ন হিসাবে, অন্যান্য পোস্টগুলি স্পষ্ট জানিয়ে দিয়েছে যে বিবৃতি প্রস্তুত করার সময় আমরা কলামের নামগুলিতে কেন মানগুলি আবদ্ধ করতে পারি না, তাই এখানে একটি সমাধান রয়েছে:

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

উপরেরটি কেবল একটি উদাহরণ, তাই বলা বাহুল্য, অনুলিপি-> পেস্ট কাজ করবে না। আপনার প্রয়োজনের জন্য সামঞ্জস্য করুন। এখন এটি 100% সুরক্ষা সরবরাহ করতে পারে না, তবে এটি কলামের নামগুলিতে কিছু নিয়ন্ত্রণের অনুমতি দেয় যখন তারা ডায়নামিক স্ট্রিং হিসাবে "আসে" এবং ব্যবহারকারীদের শেষে পরিবর্তন হতে পারে। তদ্ব্যতীত, আপনার টেবিল কলামের নাম এবং প্রকারগুলি যেহেতু তথ্য_সেমিমা থেকে বের করা হয়েছে সেগুলি নিয়ে কিছু অ্যারে তৈরি করার দরকার নেই।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.