क्या PHP PDO कथन तालिका या स्तंभ नाम को पैरामीटर के रूप में स्वीकार कर सकता है?


243

मैं तैयार पीडीओ स्टेटमेंट में तालिका का नाम क्यों नहीं दे सकता?

$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"

जवाबों:


212

तालिका और स्तंभ नाम CANNOT को PDO में मापदंडों द्वारा प्रतिस्थापित नहीं किया जा सकता है।

उस स्थिति में आप डेटा को मैन्युअल रूप से फ़िल्टर करना और पवित्र करना चाहेंगे। ऐसा करने का एक तरीका यह है कि शॉर्टहैंड मापदंडों को फ़ंक्शन में पास किया जाए जो क्वेरी को गतिशील रूप से निष्पादित करेगा और फिर switch()तालिका नाम या स्तंभ नाम के लिए उपयोग किए जाने वाले मान्य मानों की एक सफेद सूची बनाने के लिए एक कथन का उपयोग करेगा । इस तरह से कोई भी उपयोगकर्ता इनपुट सीधे क्वेरी में नहीं जाता है। उदाहरण के लिए:

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

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

कोई डिफ़ॉल्ट मामला नहीं छोड़ने या एक डिफ़ॉल्ट मामले का उपयोग करके जो एक त्रुटि संदेश देता है, आप यह सुनिश्चित करते हैं कि केवल वे मान जो आप उपयोग करना चाहते हैं, वे उपयोग करें।


17
किसी भी प्रकार की गतिशील विधि का उपयोग करने के बजाय सफेद करने के विकल्पों के लिए +1। एक अन्य विकल्प संभावित उपयोगकर्ता इनपुट (उदाहरण array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data')आदि) के अनुरूप कुंजियों के साथ सरणी के लिए स्वीकार्य तालिका नामों की मैपिंग हो सकती है
Kzqai

4
इस पर पढ़ते हुए, यह मेरे लिए होता है कि यहां उदाहरण खराब इनपुट के लिए अमान्य SQL उत्पन्न करता है, क्योंकि इसमें कोई नहीं है default। यदि इस पैटर्न का उपयोग कर रहे हैं, तो आपको या तो अपने caseएस में से एक को लेबल करना चाहिए default, या एक स्पष्ट त्रुटि मामले को जोड़ना चाहिए जैसेdefault: throw new InvalidArgumentException;
IMSoP

3
मैं एक साधारण सोच रहा था if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }। विचार के लिए धन्यवाद।
फिल ट्यून

2
मुझे याद आती है mysql_real_escape_string()। हो सकता है कि यहाँ मैं बिना किसी के उछल-कूद के यह कह सकता हूँ कि "लेकिन आपको पीडीओ के साथ इसकी आवश्यकता नहीं है"
रॉल्फ

अन्य समस्या यह है कि डायनेमिक टेबल के नाम SQL निरीक्षण को तोड़ते हैं।
एक्यरा जूल

143

यह समझने के लिए कि टेबल (या स्तंभ) नाम को बांधने से काम क्यों नहीं होता, आपको यह समझना होगा कि तैयार किए गए विवरणों में प्लेसहोल्डर कैसे काम करते हैं: उन्हें बस (उपयुक्त रूप से बच गए) तारों के रूप में प्रतिस्थापित नहीं किया जाता है, और परिणामस्वरूप SQL निष्पादित होता है। इसके बजाय, एक डीबीएमएस ने पूछा कि "तैयार" करने के लिए एक बयान पूरी क्वेरी योजना के साथ आता है कि यह उस क्वेरी को कैसे निष्पादित करेगा, जिसमें यह शामिल है कि टेबल और इंडेक्स का उपयोग कैसे किया जाएगा, जो कि आप प्लेसहोल्डर्स में कैसे भरें, इसकी परवाह किए बिना एक ही होगा।

आपके लिए SELECT name FROM my_table WHERE id = :valueजो भी विकल्प होगा, उसकी योजना समान होगी :value, लेकिन समान रूप SELECT name FROM :table WHERE id = :valueसे योजनाबद्ध नहीं किया जा सकता है, क्योंकि DBMS को पता नहीं है कि आप वास्तव में किस तालिका से चयन करने जा रहे हैं।

यह पीडीओ की तरह एक अमूर्त पुस्तकालय कुछ भी नहीं है या इसके आसपास काम करना चाहिए, या तो, क्योंकि यह तैयार किए गए कथनों के 2 प्रमुख उद्देश्यों को हरा देगा: 1) डेटाबेस को पहले से तय करने की अनुमति देने के लिए कि कोई क्वेरी कैसे चलेगी, और उसी का उपयोग करें कई बार योजना बनाएं; और 2) चर इनपुट से क्वेरी के तर्क को अलग करके सुरक्षा मुद्दों को रोकने के लिए।


1
यह सच है, लेकिन पीडीओ के तैयार स्टेटमेंट एमुलेशन (जो कि SQL ऑब्जेक्ट आइडेंटिफायर्स को कंसीव कर सकता है , के लिए खाता नहीं है , हालांकि मैं अभी भी सहमत हूं कि यह शायद नहीं होना चाहिए)।
अहग्याल

1
@eggyal मुझे लगता है कि उत्सर्जन पूरी तरह से नई कार्यक्षमता जोड़ने के बजाय सभी DBMS जायके पर मानक कार्यक्षमता का काम करने के उद्देश्य से है। पहचानकर्ताओं के लिए एक प्लेसहोल्डर को एक अलग सिंटैक्स की आवश्यकता होगी जो सीधे किसी भी डीबीएमएस द्वारा समर्थित नहीं है। पीडीओ काफी निम्न स्तर का आवरण है, और उदाहरण के लिए और एसक्यूएल पीढ़ी के लिए TOP/ LIMIT/ OFFSETक्लॉस के लिए नहीं है, इसलिए यह एक सुविधा के रूप में थोड़ी जगह होगी।
IMSoP

13

मैं देख रहा हूं कि यह एक पुराना पोस्ट है, लेकिन मुझे यह उपयोगी लगा और मैंने सोचा कि मैं @kzqai के समान एक समाधान साझा करूंगा:

मेरे पास एक फ़ंक्शन है जो दो मापदंडों को प्राप्त करता है जैसे ...

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

अंदर मैं उन सरणियों के खिलाफ जांच करता हूं जो मैंने सुनिश्चित किए हैं कि "धन्य" तालिकाओं के साथ केवल टेबल और कॉलम सुलभ हैं:

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

फिर पीडीओ चलाने से पहले PHP की जाँच करें ...

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

अधिकतर वे आदतें होती हैं जो किसी चर को बांधने वाले प्रश्नों को तैयार करते समय होती हैं। यह भी पढ़ें बार-बार कॉल तेज हैं w / निष्पादित करें यहां stackoverflow.com/questions/4700623/pdos-query-vs-execute
डॉन

आपके उदाहरण में कोई दोहराया कॉल नहीं हैं
आपका कॉमन सेंस

4

पूर्व का उपयोग करना स्वाभाविक रूप से उत्तरार्द्ध की तुलना में अधिक सुरक्षित नहीं है, आपको इनपुट को सैनिटाइज़ करने की आवश्यकता है, चाहे वह पैरामीटर सरणी का हिस्सा हो या साधारण चर। इसलिए मुझे बाद वाले फॉर्म का उपयोग करने में कुछ भी गलत नहीं दिखता है $table, बशर्ते आप यह सुनिश्चित करें कि $tableउपयोग करने से पहले सामग्री सुरक्षित है (अल्फ़ान्यूम प्लस अंडरस्कोर?)।


यह देखते हुए कि पहला विकल्प काम नहीं करेगा, आपको गतिशील क्वेरी बिल्डिंग के कुछ रूप का उपयोग करना होगा।
नूह गुडरिक

हाँ, इस सवाल का उल्लेख यह काम नहीं करेगा। मैं यह वर्णन करने की कोशिश कर रहा था कि यह इस तरह से करने की कोशिश करना बहुत महत्वपूर्ण क्यों नहीं था।
एडम बेलाएरे

3

(देर से जवाब, मेरे साइड नोट से सलाह लें)।

"डेटाबेस" बनाने की कोशिश करते समय भी यही नियम लागू होता है।

आप किसी डेटाबेस को बांधने के लिए तैयार स्टेटमेंट का उपयोग नहीं कर सकते हैं।

अर्थात:

CREATE DATABASE IF NOT EXISTS :database

काम नहीं करेगा। इसकी जगह किसी सेफेलिस्ट का इस्तेमाल करें।

साइड नोट: मैंने यह उत्तर (एक समुदाय विकि के रूप में) जोड़ा क्योंकि यह अक्सर प्रश्नों को बंद करता था, जहाँ कुछ लोग डेटाबेस को बाँधने की कोशिश में इसी तरह के प्रश्न पोस्ट करते थे, न कि टेबल और / या कॉलम।


0

यदि आप अपना स्वयं का कस्टम सैनिटाइजिंग फंक्शन प्रदान कर सकें तो मुझे आश्चर्य होगा:

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

मैं वास्तव में इसके माध्यम से नहीं सोचा है, लेकिन ऐसा लगता है कि पात्रों को छोड़कर और अंडरस्कोर काम कर सकता है।


1
MySQL टेबल के नाम में अन्य वर्ण हो सकते हैं। Dev.mysql.com/doc/refman/5.0/en/identifiers.html
फिल

@PhilLaNasa वास्तव में कुछ बचाव करना चाहिए (उन्हें संदर्भ की आवश्यकता है)। चूंकि अधिकांश DBMS एक गैर विभेदित वर्णों में नाम को संग्रहित करने के मामले में असंवेदनशील होते हैं, इसलिए: MyLongTableNameयह सही पढ़ना आसान है, लेकिन यदि आप संग्रहीत नाम की जांच करते हैं तो यह (शायद) होगा MYLONGTABLENAMEजो बहुत पठनीय नहीं है, इसलिए MY_LONG_TABLE_NAMEवास्तव में अधिक पठनीय है।
mloureiro

एक फ़ंक्शन के रूप में ऐसा न करने का एक बहुत अच्छा कारण है: आपको बहुत कम ही मनमाना इनपुट के आधार पर तालिका नाम का चयन करना चाहिए। आप लगभग निश्चित रूप से "उपयोगकर्ताओं" या "बुकिंग" को बदलने के लिए एक दुर्भावनापूर्ण उपयोगकर्ता नहीं चाहते हैं Select * From $table। एक श्वेतसूची या सख्त पैटर्न का मिलान (उदाहरण के लिए "शुरुआत के नाम_के बाद 1 से 3 अंक") वास्तव में यहां आवश्यक है।
IMSoP

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% सुरक्षा प्रदान नहीं कर सकता है, लेकिन यह स्तंभ नाम पर कुछ नियंत्रण की अनुमति देता है जब वे "गतिशील तार के रूप में" आते हैं और उपयोगकर्ताओं के अंत में बदले जा सकते हैं। इसके अलावा, आपके टेबल कॉलम के नाम और प्रकारों के साथ कुछ सरणी बनाने की कोई आवश्यकता नहीं है क्योंकि वे information_schema से निकाले जाते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.