Can PHP PDO Statements accept the table or column name as parameter?
up vote
210
down vote
favorite
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
add a comment |
up vote
210
down vote
favorite
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
add a comment |
up vote
210
down vote
favorite
up vote
210
down vote
favorite
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
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
php pdo
edited Sep 18 '15 at 16:13
Your Common Sense
1
1
asked Oct 8 '08 at 11:39
Jrgns
13.2k156274
13.2k156274
add a comment |
add a comment |
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.
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 nodefault. If using this pattern, you should either label one of yourcases asdefault, or add an explicit error case such asdefault: throw new InvalidArgumentException;
– IMSoP
Oct 22 '15 at 9:34
3
I was thinking a simpleif ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
– Phil Tune
Mar 2 '16 at 17:20
I missmysql_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
add a comment |
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.
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 forTOP/LIMIT/OFFSETclauses, 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 theprepare()method a little better.
– Phil Tune
Mar 2 '16 at 17:22
add a comment |
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);
}
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
add a comment |
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.
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
add a comment |
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.
add a comment |
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.
add a comment |
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.
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:MyLongTableNameit's easy to read right, but if you check the stored name it would (probably) beMYLONGTABLENAMEwhich isn't very readable, soMY_LONG_TABLE_NAMEis 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" intoSelect * 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
add a comment |
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.
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 nodefault. If using this pattern, you should either label one of yourcases asdefault, or add an explicit error case such asdefault: throw new InvalidArgumentException;
– IMSoP
Oct 22 '15 at 9:34
3
I was thinking a simpleif ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
– Phil Tune
Mar 2 '16 at 17:20
I missmysql_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
add a comment |
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.
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 nodefault. If using this pattern, you should either label one of yourcases asdefault, or add an explicit error case such asdefault: throw new InvalidArgumentException;
– IMSoP
Oct 22 '15 at 9:34
3
I was thinking a simpleif ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
– Phil Tune
Mar 2 '16 at 17:20
I missmysql_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
add a comment |
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.
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.
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 nodefault. If using this pattern, you should either label one of yourcases asdefault, or add an explicit error case such asdefault: throw new InvalidArgumentException;
– IMSoP
Oct 22 '15 at 9:34
3
I was thinking a simpleif ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
– Phil Tune
Mar 2 '16 at 17:20
I missmysql_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
add a comment |
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 nodefault. If using this pattern, you should either label one of yourcases asdefault, or add an explicit error case such asdefault: throw new InvalidArgumentException;
– IMSoP
Oct 22 '15 at 9:34
3
I was thinking a simpleif ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Thanks for the idea.
– Phil Tune
Mar 2 '16 at 17:20
I missmysql_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
add a comment |
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.
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 forTOP/LIMIT/OFFSETclauses, 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 theprepare()method a little better.
– Phil Tune
Mar 2 '16 at 17:22
add a comment |
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.
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 forTOP/LIMIT/OFFSETclauses, 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 theprepare()method a little better.
– Phil Tune
Mar 2 '16 at 17:22
add a comment |
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.
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.
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 forTOP/LIMIT/OFFSETclauses, 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 theprepare()method a little better.
– Phil Tune
Mar 2 '16 at 17:22
add a comment |
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 forTOP/LIMIT/OFFSETclauses, 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 theprepare()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
add a comment |
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);
}
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
add a comment |
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);
}
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
add a comment |
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);
}
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);
}
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
add a comment |
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
add a comment |
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.
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
add a comment |
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.
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
add a comment |
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.
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.
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
add a comment |
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
add a comment |
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.
add a comment |
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.
add a comment |
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.
(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.
answered Nov 8 at 15:05
community wiki
Funk Forty Niner
add a comment |
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
answered Sep 9 '14 at 15:36
man
17729
17729
add a comment |
add a comment |
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.
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:MyLongTableNameit's easy to read right, but if you check the stored name it would (probably) beMYLONGTABLENAMEwhich isn't very readable, soMY_LONG_TABLE_NAMEis 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" intoSelect * 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
add a comment |
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.
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:MyLongTableNameit's easy to read right, but if you check the stored name it would (probably) beMYLONGTABLENAMEwhich isn't very readable, soMY_LONG_TABLE_NAMEis 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" intoSelect * 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
add a comment |
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.
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.
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:MyLongTableNameit's easy to read right, but if you check the stored name it would (probably) beMYLONGTABLENAMEwhich isn't very readable, soMY_LONG_TABLE_NAMEis 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" intoSelect * 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
add a comment |
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:MyLongTableNameit's easy to read right, but if you check the stored name it would (probably) beMYLONGTABLENAMEwhich isn't very readable, soMY_LONG_TABLE_NAMEis 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" intoSelect * 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
add a comment |
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?