Can PHP PDO Statements accept the table or column name as parameter?











up vote
210
down vote

favorite
47












Why can't I pass the table name to a prepared PDO statement?



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


Is there another safe way to insert a table name into a SQL query? With safe I mean that I don't want to do



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









share|improve this question




























    up vote
    210
    down vote

    favorite
    47












    Why can't I pass the table name to a prepared PDO statement?



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


    Is there another safe way to insert a table name into a SQL query? With safe I mean that I don't want to do



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









    share|improve this question


























      up vote
      210
      down vote

      favorite
      47









      up vote
      210
      down vote

      favorite
      47






      47





      Why can't I pass the table name to a prepared PDO statement?



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


      Is there another safe way to insert a table name into a SQL query? With safe I mean that I don't want to do



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









      share|improve this question















      Why can't I pass the table name to a prepared PDO statement?



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


      Is there another safe way to insert a table name into a SQL query? With safe I mean that I don't want to do



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






      php pdo






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Sep 18 '15 at 16:13









      Your Common Sense

      1




      1










      asked Oct 8 '08 at 11:39









      Jrgns

      13.2k156274




      13.2k156274
























          7 Answers
          7






          active

          oldest

          votes

















          up vote
          183
          down vote



          accepted










          Table and Column names cannot be replaced by parameters in PDO.



          In that case you will simply want to filter and sanitize the data manually. One way to do this is to pass in shorthand parameters to the function that will execute the query dynamically and then use a switch() statement to create a white list of valid values to be used for the table name or column name. That way no user input ever goes directly into the query. So for example:



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

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


          By leaving no default case or using a default case that returns an error message you ensure that only values that you want used get used.






          share|improve this answer



















          • 13




            +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
            – Kzqai
            Dec 22 '11 at 18:05








          • 2




            Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
            – IMSoP
            Oct 22 '15 at 9:34






          • 3




            I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
            – Phil Tune
            Mar 2 '16 at 17:20












          • I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
            – Rolf
            Oct 20 at 8:39


















          up vote
          127
          down vote













          To understand why binding a table (or column) name doesn't work, you have to understand how the placeholders in prepared statements work: they are not simply substituted in as (suitably escaped) strings, and the resulting SQL executed. Instead, a DBMS asked to "prepare" a statement comes up with a complete query plan for how it would execute that query, including which tables and indexes it would use, which will be the same regardless of how you fill in the placeholders.



          The plan for SELECT name FROM my_table WHERE id = :value will be the same whatever you substitute for :value, but the seemingly similar SELECT name FROM :table WHERE id = :value cannot be planned, because the DBMS has no idea what table you're actually going to select from.



          This is not something an abstraction library like PDO can or should work around, either, since it would defeat the 2 key purposes of prepared statements: 1) to allow the database to decide in advance how a query will be run, and use the same plan multiple times; and 2) to prevent security issues by separating the logic of the query from the variable input.






          share|improve this answer























          • True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
            – eggyal
            Dec 27 '13 at 19:40






          • 1




            @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
            – IMSoP
            Jan 1 '14 at 19:53








          • 1




            +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
            – Phil Tune
            Mar 2 '16 at 17:22


















          up vote
          12
          down vote













          I see this is an old post, but I found it useful and thought I'd share a solution similar to what @kzqai suggested:



          I have a function that receives two parameters like...



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


          Inside I check against arrays I've set up to make sure only tables and columns with "blessed" tables are accessible:



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


          Then the PHP check before running PDO looks like...



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





          share|improve this answer

















          • 2




            good for short solution, but why not just $pdo->query($sql)
            – jscripter
            Dec 15 '14 at 1:32










          • Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
            – Don
            Apr 13 '15 at 13:33










          • there are no repeated calls in your example
            – Your Common Sense
            Mar 21 at 7:27


















          up vote
          4
          down vote













          Using the former isn't inherently more safe than the latter, you need to sanitize the input whether it's part of a parameter array or a simple variable. So I don't see anything wrong with using the latter form with $table, provided you make sure that the content of $table is safe (alphanum plus underscores?) before using it.






          share|improve this answer





















          • Considering that the first option won't work, you have to use some form of dynamic query building.
            – Noah Goodrich
            Oct 8 '08 at 11:58










          • Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
            – Adam Bellaire
            Oct 8 '08 at 12:01


















          up vote
          1
          down vote













          (Late answer, consult my side note).



          The same rule applies when trying to create a "database".



          You cannot use a prepared statement to bind a database.



          I.e.:



          CREATE DATABASE IF NOT EXISTS :database


          will not work. Use a safelist instead.



          Side note: I added this answer (as a community wiki) because it often used to close questions with, where some people posted questions similar to this in trying to bind a database and not a table and/or column.






          share|improve this answer






























            up vote
            0
            down vote













            As for the main question in this thread, the other posts made it clear why we can't bind values to column names when preparing statements, so here is one solution:



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


            The above is just an example, so needless to say, copy->paste won't work. Adjust for your needs.
            Now this may not provide 100% security, but it allows some control over the column names when they "come in" as dynamic strings and may be changed on users end. Furthermore, there is no need to build some array with your table column names and types since they are extracted from the information_schema.






            share|improve this answer




























              up vote
              -1
              down vote













              Part of me wonders if you could provide your own custom sanitizing function as simple as this:



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


              I haven't really thought through it, but it seems like removing anything except characters and underscores might work.






              share|improve this answer

















              • 1




                MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                – Phil
                Apr 29 '14 at 1:31








              • 1




                But should they? ;)
                – Phil LaNasa
                Apr 30 '15 at 19:56










              • @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                – mloureiro
                Aug 10 '15 at 17:26










              • There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                – IMSoP
                Mar 21 '17 at 11:55










              protected by Samuel Liew Oct 5 '15 at 9:01



              Thank you for your interest in this question.
              Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).



              Would you like to answer one of these unanswered questions instead?














              7 Answers
              7






              active

              oldest

              votes








              7 Answers
              7






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes








              up vote
              183
              down vote



              accepted










              Table and Column names cannot be replaced by parameters in PDO.



              In that case you will simply want to filter and sanitize the data manually. One way to do this is to pass in shorthand parameters to the function that will execute the query dynamically and then use a switch() statement to create a white list of valid values to be used for the table name or column name. That way no user input ever goes directly into the query. So for example:



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

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


              By leaving no default case or using a default case that returns an error message you ensure that only values that you want used get used.






              share|improve this answer



















              • 13




                +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
                – Kzqai
                Dec 22 '11 at 18:05








              • 2




                Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
                – IMSoP
                Oct 22 '15 at 9:34






              • 3




                I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
                – Phil Tune
                Mar 2 '16 at 17:20












              • I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
                – Rolf
                Oct 20 at 8:39















              up vote
              183
              down vote



              accepted










              Table and Column names cannot be replaced by parameters in PDO.



              In that case you will simply want to filter and sanitize the data manually. One way to do this is to pass in shorthand parameters to the function that will execute the query dynamically and then use a switch() statement to create a white list of valid values to be used for the table name or column name. That way no user input ever goes directly into the query. So for example:



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

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


              By leaving no default case or using a default case that returns an error message you ensure that only values that you want used get used.






              share|improve this answer



















              • 13




                +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
                – Kzqai
                Dec 22 '11 at 18:05








              • 2




                Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
                – IMSoP
                Oct 22 '15 at 9:34






              • 3




                I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
                – Phil Tune
                Mar 2 '16 at 17:20












              • I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
                – Rolf
                Oct 20 at 8:39













              up vote
              183
              down vote



              accepted







              up vote
              183
              down vote



              accepted






              Table and Column names cannot be replaced by parameters in PDO.



              In that case you will simply want to filter and sanitize the data manually. One way to do this is to pass in shorthand parameters to the function that will execute the query dynamically and then use a switch() statement to create a white list of valid values to be used for the table name or column name. That way no user input ever goes directly into the query. So for example:



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

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


              By leaving no default case or using a default case that returns an error message you ensure that only values that you want used get used.






              share|improve this answer














              Table and Column names cannot be replaced by parameters in PDO.



              In that case you will simply want to filter and sanitize the data manually. One way to do this is to pass in shorthand parameters to the function that will execute the query dynamically and then use a switch() statement to create a white list of valid values to be used for the table name or column name. That way no user input ever goes directly into the query. So for example:



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

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


              By leaving no default case or using a default case that returns an error message you ensure that only values that you want used get used.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Mar 21 at 7:21









              Your Common Sense

              1




              1










              answered Oct 8 '08 at 11:57









              Noah Goodrich

              19.5k125686




              19.5k125686








              • 13




                +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
                – Kzqai
                Dec 22 '11 at 18:05








              • 2




                Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
                – IMSoP
                Oct 22 '15 at 9:34






              • 3




                I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
                – Phil Tune
                Mar 2 '16 at 17:20












              • I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
                – Rolf
                Oct 20 at 8:39














              • 13




                +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
                – Kzqai
                Dec 22 '11 at 18:05








              • 2




                Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
                – IMSoP
                Oct 22 '15 at 9:34






              • 3




                I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
                – Phil Tune
                Mar 2 '16 at 17:20












              • I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
                – Rolf
                Oct 20 at 8:39








              13




              13




              +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
              – Kzqai
              Dec 22 '11 at 18:05






              +1 for whitelisting options instead of using any kind of dynamic method. Another alternative might be mapping acceptable table names to an array with keys that correspond to the potential user input (e.g. array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data') etc.)
              – Kzqai
              Dec 22 '11 at 18:05






              2




              2




              Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
              – IMSoP
              Oct 22 '15 at 9:34




              Reading over this, it occurs to me that the example here generates invalid SQL for bad input, because it has no default. If using this pattern, you should either label one of your cases as default, or add an explicit error case such as default: throw new InvalidArgumentException;
              – IMSoP
              Oct 22 '15 at 9:34




              3




              3




              I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
              – Phil Tune
              Mar 2 '16 at 17:20






              I was thinking a simple if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
              – Phil Tune
              Mar 2 '16 at 17:20














              I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
              – Rolf
              Oct 20 at 8:39




              I miss mysql_real_escape_string(). Maybe here I can say it without someone jumping in and saying "But you don't need it with PDO"
              – Rolf
              Oct 20 at 8:39












              up vote
              127
              down vote













              To understand why binding a table (or column) name doesn't work, you have to understand how the placeholders in prepared statements work: they are not simply substituted in as (suitably escaped) strings, and the resulting SQL executed. Instead, a DBMS asked to "prepare" a statement comes up with a complete query plan for how it would execute that query, including which tables and indexes it would use, which will be the same regardless of how you fill in the placeholders.



              The plan for SELECT name FROM my_table WHERE id = :value will be the same whatever you substitute for :value, but the seemingly similar SELECT name FROM :table WHERE id = :value cannot be planned, because the DBMS has no idea what table you're actually going to select from.



              This is not something an abstraction library like PDO can or should work around, either, since it would defeat the 2 key purposes of prepared statements: 1) to allow the database to decide in advance how a query will be run, and use the same plan multiple times; and 2) to prevent security issues by separating the logic of the query from the variable input.






              share|improve this answer























              • True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
                – eggyal
                Dec 27 '13 at 19:40






              • 1




                @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
                – IMSoP
                Jan 1 '14 at 19:53








              • 1




                +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
                – Phil Tune
                Mar 2 '16 at 17:22















              up vote
              127
              down vote













              To understand why binding a table (or column) name doesn't work, you have to understand how the placeholders in prepared statements work: they are not simply substituted in as (suitably escaped) strings, and the resulting SQL executed. Instead, a DBMS asked to "prepare" a statement comes up with a complete query plan for how it would execute that query, including which tables and indexes it would use, which will be the same regardless of how you fill in the placeholders.



              The plan for SELECT name FROM my_table WHERE id = :value will be the same whatever you substitute for :value, but the seemingly similar SELECT name FROM :table WHERE id = :value cannot be planned, because the DBMS has no idea what table you're actually going to select from.



              This is not something an abstraction library like PDO can or should work around, either, since it would defeat the 2 key purposes of prepared statements: 1) to allow the database to decide in advance how a query will be run, and use the same plan multiple times; and 2) to prevent security issues by separating the logic of the query from the variable input.






              share|improve this answer























              • True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
                – eggyal
                Dec 27 '13 at 19:40






              • 1




                @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
                – IMSoP
                Jan 1 '14 at 19:53








              • 1




                +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
                – Phil Tune
                Mar 2 '16 at 17:22













              up vote
              127
              down vote










              up vote
              127
              down vote









              To understand why binding a table (or column) name doesn't work, you have to understand how the placeholders in prepared statements work: they are not simply substituted in as (suitably escaped) strings, and the resulting SQL executed. Instead, a DBMS asked to "prepare" a statement comes up with a complete query plan for how it would execute that query, including which tables and indexes it would use, which will be the same regardless of how you fill in the placeholders.



              The plan for SELECT name FROM my_table WHERE id = :value will be the same whatever you substitute for :value, but the seemingly similar SELECT name FROM :table WHERE id = :value cannot be planned, because the DBMS has no idea what table you're actually going to select from.



              This is not something an abstraction library like PDO can or should work around, either, since it would defeat the 2 key purposes of prepared statements: 1) to allow the database to decide in advance how a query will be run, and use the same plan multiple times; and 2) to prevent security issues by separating the logic of the query from the variable input.






              share|improve this answer














              To understand why binding a table (or column) name doesn't work, you have to understand how the placeholders in prepared statements work: they are not simply substituted in as (suitably escaped) strings, and the resulting SQL executed. Instead, a DBMS asked to "prepare" a statement comes up with a complete query plan for how it would execute that query, including which tables and indexes it would use, which will be the same regardless of how you fill in the placeholders.



              The plan for SELECT name FROM my_table WHERE id = :value will be the same whatever you substitute for :value, but the seemingly similar SELECT name FROM :table WHERE id = :value cannot be planned, because the DBMS has no idea what table you're actually going to select from.



              This is not something an abstraction library like PDO can or should work around, either, since it would defeat the 2 key purposes of prepared statements: 1) to allow the database to decide in advance how a query will be run, and use the same plan multiple times; and 2) to prevent security issues by separating the logic of the query from the variable input.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Nov 10 '13 at 18:08

























              answered Apr 13 '13 at 17:04









              IMSoP

              45.3k65692




              45.3k65692












              • True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
                – eggyal
                Dec 27 '13 at 19:40






              • 1




                @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
                – IMSoP
                Jan 1 '14 at 19:53








              • 1




                +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
                – Phil Tune
                Mar 2 '16 at 17:22


















              • True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
                – eggyal
                Dec 27 '13 at 19:40






              • 1




                @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
                – IMSoP
                Jan 1 '14 at 19:53








              • 1




                +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
                – Phil Tune
                Mar 2 '16 at 17:22
















              True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
              – eggyal
              Dec 27 '13 at 19:40




              True, but does not account for PDO's prepare statement emulation (which could conceivably parameterise SQL object identifiers, albeit I still agree that it probably shouldn't).
              – eggyal
              Dec 27 '13 at 19:40




              1




              1




              @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
              – IMSoP
              Jan 1 '14 at 19:53






              @eggyal I guess the emulation is aimed at making standard functionality work on all DBMS flavours, rather than adding completely new functionality. A placeholder for identifiers would also need a distinct syntax not directly supported by any DBMS. PDO is quite a low-level wrapper, and doesn't for instance offer and SQL generation for TOP/LIMIT/OFFSET clauses, so this would be a bit out of place as a feature.
              – IMSoP
              Jan 1 '14 at 19:53






              1




              1




              +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
              – Phil Tune
              Mar 2 '16 at 17:22




              +1 for answering the "why". I figured this was probably why, and this helps me understand the prepare() method a little better.
              – Phil Tune
              Mar 2 '16 at 17:22










              up vote
              12
              down vote













              I see this is an old post, but I found it useful and thought I'd share a solution similar to what @kzqai suggested:



              I have a function that receives two parameters like...



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


              Inside I check against arrays I've set up to make sure only tables and columns with "blessed" tables are accessible:



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


              Then the PHP check before running PDO looks like...



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





              share|improve this answer

















              • 2




                good for short solution, but why not just $pdo->query($sql)
                – jscripter
                Dec 15 '14 at 1:32










              • Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
                – Don
                Apr 13 '15 at 13:33










              • there are no repeated calls in your example
                – Your Common Sense
                Mar 21 at 7:27















              up vote
              12
              down vote













              I see this is an old post, but I found it useful and thought I'd share a solution similar to what @kzqai suggested:



              I have a function that receives two parameters like...



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


              Inside I check against arrays I've set up to make sure only tables and columns with "blessed" tables are accessible:



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


              Then the PHP check before running PDO looks like...



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





              share|improve this answer

















              • 2




                good for short solution, but why not just $pdo->query($sql)
                – jscripter
                Dec 15 '14 at 1:32










              • Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
                – Don
                Apr 13 '15 at 13:33










              • there are no repeated calls in your example
                – Your Common Sense
                Mar 21 at 7:27













              up vote
              12
              down vote










              up vote
              12
              down vote









              I see this is an old post, but I found it useful and thought I'd share a solution similar to what @kzqai suggested:



              I have a function that receives two parameters like...



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


              Inside I check against arrays I've set up to make sure only tables and columns with "blessed" tables are accessible:



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


              Then the PHP check before running PDO looks like...



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





              share|improve this answer












              I see this is an old post, but I found it useful and thought I'd share a solution similar to what @kzqai suggested:



              I have a function that receives two parameters like...



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


              Inside I check against arrays I've set up to make sure only tables and columns with "blessed" tables are accessible:



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


              Then the PHP check before running PDO looks like...



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






              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered Apr 30 '13 at 17:30









              Don

              1,13631934




              1,13631934








              • 2




                good for short solution, but why not just $pdo->query($sql)
                – jscripter
                Dec 15 '14 at 1:32










              • Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
                – Don
                Apr 13 '15 at 13:33










              • there are no repeated calls in your example
                – Your Common Sense
                Mar 21 at 7:27














              • 2




                good for short solution, but why not just $pdo->query($sql)
                – jscripter
                Dec 15 '14 at 1:32










              • Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
                – Don
                Apr 13 '15 at 13:33










              • there are no repeated calls in your example
                – Your Common Sense
                Mar 21 at 7:27








              2




              2




              good for short solution, but why not just $pdo->query($sql)
              – jscripter
              Dec 15 '14 at 1:32




              good for short solution, but why not just $pdo->query($sql)
              – jscripter
              Dec 15 '14 at 1:32












              Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
              – Don
              Apr 13 '15 at 13:33




              Mostly out of habit when preparing queries that have to bind a variable. Also read repeated calls are faster w/ execute here stackoverflow.com/questions/4700623/pdos-query-vs-execute
              – Don
              Apr 13 '15 at 13:33












              there are no repeated calls in your example
              – Your Common Sense
              Mar 21 at 7:27




              there are no repeated calls in your example
              – Your Common Sense
              Mar 21 at 7:27










              up vote
              4
              down vote













              Using the former isn't inherently more safe than the latter, you need to sanitize the input whether it's part of a parameter array or a simple variable. So I don't see anything wrong with using the latter form with $table, provided you make sure that the content of $table is safe (alphanum plus underscores?) before using it.






              share|improve this answer





















              • Considering that the first option won't work, you have to use some form of dynamic query building.
                – Noah Goodrich
                Oct 8 '08 at 11:58










              • Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
                – Adam Bellaire
                Oct 8 '08 at 12:01















              up vote
              4
              down vote













              Using the former isn't inherently more safe than the latter, you need to sanitize the input whether it's part of a parameter array or a simple variable. So I don't see anything wrong with using the latter form with $table, provided you make sure that the content of $table is safe (alphanum plus underscores?) before using it.






              share|improve this answer





















              • Considering that the first option won't work, you have to use some form of dynamic query building.
                – Noah Goodrich
                Oct 8 '08 at 11:58










              • Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
                – Adam Bellaire
                Oct 8 '08 at 12:01













              up vote
              4
              down vote










              up vote
              4
              down vote









              Using the former isn't inherently more safe than the latter, you need to sanitize the input whether it's part of a parameter array or a simple variable. So I don't see anything wrong with using the latter form with $table, provided you make sure that the content of $table is safe (alphanum plus underscores?) before using it.






              share|improve this answer












              Using the former isn't inherently more safe than the latter, you need to sanitize the input whether it's part of a parameter array or a simple variable. So I don't see anything wrong with using the latter form with $table, provided you make sure that the content of $table is safe (alphanum plus underscores?) before using it.







              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered Oct 8 '08 at 11:46









              Adam Bellaire

              78.8k19138156




              78.8k19138156












              • Considering that the first option won't work, you have to use some form of dynamic query building.
                – Noah Goodrich
                Oct 8 '08 at 11:58










              • Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
                – Adam Bellaire
                Oct 8 '08 at 12:01


















              • Considering that the first option won't work, you have to use some form of dynamic query building.
                – Noah Goodrich
                Oct 8 '08 at 11:58










              • Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
                – Adam Bellaire
                Oct 8 '08 at 12:01
















              Considering that the first option won't work, you have to use some form of dynamic query building.
              – Noah Goodrich
              Oct 8 '08 at 11:58




              Considering that the first option won't work, you have to use some form of dynamic query building.
              – Noah Goodrich
              Oct 8 '08 at 11:58












              Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
              – Adam Bellaire
              Oct 8 '08 at 12:01




              Yes, the question mentioned it won't work. I was trying to describe why it wasn't terribly important to even try to do it that way.
              – Adam Bellaire
              Oct 8 '08 at 12:01










              up vote
              1
              down vote













              (Late answer, consult my side note).



              The same rule applies when trying to create a "database".



              You cannot use a prepared statement to bind a database.



              I.e.:



              CREATE DATABASE IF NOT EXISTS :database


              will not work. Use a safelist instead.



              Side note: I added this answer (as a community wiki) because it often used to close questions with, where some people posted questions similar to this in trying to bind a database and not a table and/or column.






              share|improve this answer



























                up vote
                1
                down vote













                (Late answer, consult my side note).



                The same rule applies when trying to create a "database".



                You cannot use a prepared statement to bind a database.



                I.e.:



                CREATE DATABASE IF NOT EXISTS :database


                will not work. Use a safelist instead.



                Side note: I added this answer (as a community wiki) because it often used to close questions with, where some people posted questions similar to this in trying to bind a database and not a table and/or column.






                share|improve this answer

























                  up vote
                  1
                  down vote










                  up vote
                  1
                  down vote









                  (Late answer, consult my side note).



                  The same rule applies when trying to create a "database".



                  You cannot use a prepared statement to bind a database.



                  I.e.:



                  CREATE DATABASE IF NOT EXISTS :database


                  will not work. Use a safelist instead.



                  Side note: I added this answer (as a community wiki) because it often used to close questions with, where some people posted questions similar to this in trying to bind a database and not a table and/or column.






                  share|improve this answer














                  (Late answer, consult my side note).



                  The same rule applies when trying to create a "database".



                  You cannot use a prepared statement to bind a database.



                  I.e.:



                  CREATE DATABASE IF NOT EXISTS :database


                  will not work. Use a safelist instead.



                  Side note: I added this answer (as a community wiki) because it often used to close questions with, where some people posted questions similar to this in trying to bind a database and not a table and/or column.







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  answered Nov 8 at 15:05


























                  community wiki





                  Funk Forty Niner























                      up vote
                      0
                      down vote













                      As for the main question in this thread, the other posts made it clear why we can't bind values to column names when preparing statements, so here is one solution:



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


                      The above is just an example, so needless to say, copy->paste won't work. Adjust for your needs.
                      Now this may not provide 100% security, but it allows some control over the column names when they "come in" as dynamic strings and may be changed on users end. Furthermore, there is no need to build some array with your table column names and types since they are extracted from the information_schema.






                      share|improve this answer

























                        up vote
                        0
                        down vote













                        As for the main question in this thread, the other posts made it clear why we can't bind values to column names when preparing statements, so here is one solution:



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


                        The above is just an example, so needless to say, copy->paste won't work. Adjust for your needs.
                        Now this may not provide 100% security, but it allows some control over the column names when they "come in" as dynamic strings and may be changed on users end. Furthermore, there is no need to build some array with your table column names and types since they are extracted from the information_schema.






                        share|improve this answer























                          up vote
                          0
                          down vote










                          up vote
                          0
                          down vote









                          As for the main question in this thread, the other posts made it clear why we can't bind values to column names when preparing statements, so here is one solution:



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


                          The above is just an example, so needless to say, copy->paste won't work. Adjust for your needs.
                          Now this may not provide 100% security, but it allows some control over the column names when they "come in" as dynamic strings and may be changed on users end. Furthermore, there is no need to build some array with your table column names and types since they are extracted from the information_schema.






                          share|improve this answer












                          As for the main question in this thread, the other posts made it clear why we can't bind values to column names when preparing statements, so here is one solution:



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


                          The above is just an example, so needless to say, copy->paste won't work. Adjust for your needs.
                          Now this may not provide 100% security, but it allows some control over the column names when they "come in" as dynamic strings and may be changed on users end. Furthermore, there is no need to build some array with your table column names and types since they are extracted from the information_schema.







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered Sep 9 '14 at 15:36









                          man

                          17729




                          17729






















                              up vote
                              -1
                              down vote













                              Part of me wonders if you could provide your own custom sanitizing function as simple as this:



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


                              I haven't really thought through it, but it seems like removing anything except characters and underscores might work.






                              share|improve this answer

















                              • 1




                                MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                                – Phil
                                Apr 29 '14 at 1:31








                              • 1




                                But should they? ;)
                                – Phil LaNasa
                                Apr 30 '15 at 19:56










                              • @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                                – mloureiro
                                Aug 10 '15 at 17:26










                              • There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                                – IMSoP
                                Mar 21 '17 at 11:55















                              up vote
                              -1
                              down vote













                              Part of me wonders if you could provide your own custom sanitizing function as simple as this:



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


                              I haven't really thought through it, but it seems like removing anything except characters and underscores might work.






                              share|improve this answer

















                              • 1




                                MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                                – Phil
                                Apr 29 '14 at 1:31








                              • 1




                                But should they? ;)
                                – Phil LaNasa
                                Apr 30 '15 at 19:56










                              • @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                                – mloureiro
                                Aug 10 '15 at 17:26










                              • There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                                – IMSoP
                                Mar 21 '17 at 11:55













                              up vote
                              -1
                              down vote










                              up vote
                              -1
                              down vote









                              Part of me wonders if you could provide your own custom sanitizing function as simple as this:



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


                              I haven't really thought through it, but it seems like removing anything except characters and underscores might work.






                              share|improve this answer












                              Part of me wonders if you could provide your own custom sanitizing function as simple as this:



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


                              I haven't really thought through it, but it seems like removing anything except characters and underscores might work.







                              share|improve this answer












                              share|improve this answer



                              share|improve this answer










                              answered Apr 29 '14 at 0:21









                              Phil LaNasa

                              2,3281814




                              2,3281814








                              • 1




                                MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                                – Phil
                                Apr 29 '14 at 1:31








                              • 1




                                But should they? ;)
                                – Phil LaNasa
                                Apr 30 '15 at 19:56










                              • @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                                – mloureiro
                                Aug 10 '15 at 17:26










                              • There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                                – IMSoP
                                Mar 21 '17 at 11:55














                              • 1




                                MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                                – Phil
                                Apr 29 '14 at 1:31








                              • 1




                                But should they? ;)
                                – Phil LaNasa
                                Apr 30 '15 at 19:56










                              • @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                                – mloureiro
                                Aug 10 '15 at 17:26










                              • There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                                – IMSoP
                                Mar 21 '17 at 11:55








                              1




                              1




                              MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                              – Phil
                              Apr 29 '14 at 1:31






                              MySQL table names can contain other characters. See dev.mysql.com/doc/refman/5.0/en/identifiers.html
                              – Phil
                              Apr 29 '14 at 1:31






                              1




                              1




                              But should they? ;)
                              – Phil LaNasa
                              Apr 30 '15 at 19:56




                              But should they? ;)
                              – Phil LaNasa
                              Apr 30 '15 at 19:56












                              @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                              – mloureiro
                              Aug 10 '15 at 17:26




                              @PhilLaNasa actually some defend they should (need's reference). Since most of DBMS are case insensitive storing the name in a non differentiated characters, ex: MyLongTableName it's easy to read right, but if you check the stored name it would (probably) be MYLONGTABLENAME which isn't very readable, so MY_LONG_TABLE_NAME is actually more readable.
                              – mloureiro
                              Aug 10 '15 at 17:26












                              There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                              – IMSoP
                              Mar 21 '17 at 11:55




                              There is a very good reason not to have this as a function: you should very very rarely be selecting a table name based on arbitrary input. You almost certainly don't want a malicious user to substitute "users" or "bookings" into Select * From $table. A whitelist or strict pattern match (e.g. "names beginning report_ followed by 1 to 3 digits only") really is essential here.
                              – IMSoP
                              Mar 21 '17 at 11:55





                              protected by Samuel Liew Oct 5 '15 at 9:01



                              Thank you for your interest in this question.
                              Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).



                              Would you like to answer one of these unanswered questions instead?



                              Popular posts from this blog

                              Tonle Sap (See)

                              I get strange results when I access the Sqlitedatabase with Unity C# via XAMPP

                              Guatemaltekische Davis-Cup-Mannschaft