as a newbie, I've followed PHP MySQL tutorials advising the use of regular MySQL php functions. However, since I've been told that PDO is the better alternative, I've been converting my code to that. I just ran into the following problem:
$query = $uspdb->prepare("SELECT post_id, is_approved, reports FROM ? WHERE id=? AND ?");
$query->bindValue(1, $table, PDO::PARAM_INT);
$query->bindValue(2, $id, PDO::PARAM_INT);
$query->bindValue(3, checkPermission("comment_moderation"),PDO::PARAM_BOOL);
$query->execute;
$result = $query->fetch(PDO::FETCH_ASSOC);
The first line throws the following PDO exception:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '? WHERE id=? AND ?' at line 1
Why is that? I have no idea what could be wrong with the syntax. The tutorial I'm reading tells me that I should be using bindValue or execute(array(stuff)) to add parameters rather than ".$id." and the likes, since it's safer, but this isn't working for whatever reason.
Unfortunately, prepared statement can represent a data literal only. (in case of emulated prepares).
So, a developer have to take care of identifiers oneself - PDO offers no help for this matter.
To make a dynamical identifier safe, one have to follow 2 strict rules:
To format identifier properly. Means
enclose identifier in backticks.
escape backticks inside by doubling them.
To verify it against a hardcoded whitelist.
After the formatting, it is safe to insert the $table variable into query. So, the code would be:
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
However, although such a formatting would be enough for the cases like ORDER BY, for the most other cases there is a possibility for a different sort of injection: letting a user to choose a table or a field they can see, we may reveal some sensitive information, like password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:
$allowed = array("name","price","qty");
$key = array_search($_GET['field'], $allowed));
if ($key === false) {
throw new Exception('Wrong field name');
}
$field = $allowed[$key];
$query = "SELECT $field FROM t"; //value is safe
As is typical, I solve my problem seconds after posing the question.
The problem is that you can only bind key values like this, not table or column names. I'll have to keep inserting the table and column names manually just as before:
$query = $uspdb->prepare("SELECT post_id, is_approved, reports FROM $table WHERE id=? AND ?");
$query->execute(array($id,checkPermission("comment_moderation")));
$result = $query->fetch(PDO::FETCH_ASSOC);
If the table or column name is left to the user's discretion, you should go through additional steps to sanitize it, which are detailed in Your Common Sense's response above. In my case it was the following code:
$type = $_GET[type];
switch($type) {
case "review":
$table = "site_cmt_reviews";
break;
default:
$table = "site_cmt_articles";
}
Still, thanks for reading!
Related
I am working on a user login feature and facing a strange problem "Invalid Number of Columns". I Google'd it and many people have the same issue and their question was bit different.
Here is my code:
//this is a function inside user class. And function receives $user_data array
try{
$stmt = $this->db_connection->prepare("SELECT `id` FROM `aaa_users` WHERE (`user_email` = :user_email OR `user_name` = :user_email) AND `user_pass` = :user_pass");
$stmt->bindparam(':user_email', $user_data['email']);
$stmt->bindparam(':user_pass', $user_data['password']);
$stmt->execute();
$count = $stmt->rowCount();
} catch (PDOException $e){
echo $e->getMessage();
}
The registration query works but this throws an exception. I wonder if there might be tiny mistake but I can't figure it out.
As I mentioned in comments, PDO's emulation may not be enabled on your server and using the same named placeholder (may be) causing this to error out.
I've respectively renamed both :user_mail to :user_email_1 and :user_email_2.
$stmt = $this->db_connection->prepare("
SELECT `id` FROM `aaa_users`
WHERE (`user_email` = :user_email_1
OR `user_name` = :user_email_2)
AND `user_pass` = :user_pass
");
$stmt->bindparam(':user_email_1', $user_data['email']);
$stmt->bindparam(':user_email_2', $user_data['email']);
$stmt->bindparam(':user_pass', $user_data['password']);
$stmt->execute();
You can read more about this in the following Q&A on Stack:
Support server side prepared statements with PDO?
The accepted answer inside it, offers a good explanation.
Note: Make sure that both all values related to $user_data['X_values'] contain value. This suggests that it may be coming from a previous query and is unknown as to their origins / values.
Use error checking by following the below links, if any of them have not already been used during testing:
http://php.net/manual/en/pdo.error-handling.php
http://php.net/manual/en/function.error-reporting.php
While making sure that all columns do in fact exist. There could be the slightest chance that your database/table stand to be case-sensitive, so check for that (letter case) also.
Another thing; it has happened in the past where people actually had to use bindParam in Camel Case instead of bindparam all in lower case; it's a possibility.
So I have the following
public function search($table, $column, $term, $limit = 5){
$command = "SELECT name FROM `$table` WHERE `:col` LIKE :term LIMIT :lim";
$query = $this->connection->prepare($command);
$query->execute(array(":term"=>"%{$term}%", ':lim'=>$limit, ':col' => $column));
print_r($query->fetchAll());
}
and the return value is
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''5'' at line 1
From the duplicate, I changed the execute to this
$command = "SELECT name FROM `$table` WHERE ? LIKE ? LIMIT ?";
...
$query->bindParam(1, $column, PDO::PARAM_STR);
$term = "%{$term}%";
$query->bindParam(2, $term, PDO::PARAM_STR);
$query->bindParam(3, $limit, PDO::PARAM_INT);
$query->execute();
And now I am getting this:
Array ( )
Is my limit now 0 or something? What's going on?
It looks like you're trying to bind a column in your query, which you're not allowed to do.
Prepared statements are awesome for two reasons:
They separate your query from your data values, preventing any possibility of SQL injection
They allow the same query and query execution plan to be used multiple times with many different data, which is much more efficient.
As a query plan is based on information such as which columns are used, what columns have indexes on them which can be used, what tables to retrieve data from, a query plan is specific to which columns are retrieved. For SQL injection protection, it's also necessary to prevent calling of functions, or retrieving columns which may or may not be displayed to the user to prevent data leakage. Both of these points mean that prepared statements with bound parameters cannot provide anything other than data, which columns are not.
This is why you're having issues binding a column - your query isn't throwing an error because you're actually allowed to compare two values which aren't column data.
A somewhat nasty way around this is to include a conditional for every column you want to search:
SELECT * FROM table
WHERE
(:column = "colA" and colA like :term)
OR (:column = "colB" AND colB like :term)
OR ...
;
I do understand that the prepared statements is the ultimate way to seek protection against the SQL injection. However, they provide coverage in a limited fashion; for example, in cases where I let the user to decide how the order by operation to be ( i.e, is it ASC or DESC? etc ), I get no coverage there with the prepared statements.
I understand that I can map the user input to a pre-defined white list for that. But, this is only possible when a whitelist can be created or guessed thoroughly beforehand.
For example, in the cases I mention above ( the ASC, or DESC ), this can easily be mapped and verified against a list of accepted values. But isn't there a situation where the portion of the SQL statement cannot be verified against a white list?
If such a situation exists, then what's the recommended approach?
If I were to escape the user_input using the underlying database's built-in escape utility (such as mysqL_real_escape_string for mysql) across the board, where would I fail?
I'm asking this question with the assumption that I always construct my sql statements with quoted values - even for integers...
Let's take a look at the following example and reflect upon it..
select {$fields} from {$table} where Age='{$age}' order by {$orderby_pref}
Assume all vars are user supplied.
If I were to mysql_real_escape_string all the variables in the above SQL ( as opposed to using prepared statements which covers me only half-way forcing me to come up whitelists for the other half that it cannot help), wouldn't it be equally safe (and easier to code)? If not, in which input scenario escape utility would fail?
$fields = mysql_escape($fields);
$table = mysql_escape($table);
$age = mysql_escape($age);
$orderby_pref = mysql_escape($orderby_pref);
select {$fields} from {$table} where Age='{$age}' order by {$orderby_pref}
You always need to use white-lists for stuff like table- or column names, whether you use prepared statements or the mysql escape functions.
The problem is that table names and column names are not quoted in single or double quotes, so if you use a function that specifically quotes these characters (and some more of course...), it will do nothing for your table name.
Consider the table name my_table; DELETE * FROM mysql; SELECT * FROM my_table. Nothing in this string will get escaped by mysql's escape functions but it is definitely a string you would want to check against a white-list.
Apart from that the mysql escape functions have a problem with character sets that can render them useless, so you are always better off with prepared statements.
You could use PDO and your life will get easier ... :
# Order
switch(strtoupper($Order)){
default:
case 'ASC':
$Order = 'ASC';
break;
case 'DESC':
$Order = 'DESC';
break;
}
# ID
$ID = 39;
$Username = 'David';
# Query
$Query = $this->DB->Main->prepare('SELECT * FROM Table WHERE ID = :ID AND Username = :Username ORDER BY HellBob '.$Order);
$Query->bindValue(':ID', $ID, PDO::PARAM_INT);
$Query->bindValue(':Username', $Username, PDO::PARAM_STR);
# All good ?
if(!$Query->execute()){
exit('Error');
}
// Results
$Row = $Query->fetch(PDO::FETCH_ASSOC);
You don't have to worry about quotes or SQL injections. You can use simple "white list" as you mention to get variable into your query.
I want to use a prepared statement in which the passed-in parameters are for the ORDER BY and LIMIT clauses, like so:
$sql = 'SELECT * FROM table ORDER BY :sort :dir LIMIT :start, :results';
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
'sort' => $_GET['sort'],
'dir' => $_GET['dir'],
'start' => $_GET['start'],
'results' => $_GET['results'],
)
);
But $stmt->fetchAll(PDO::FETCH_ASSOC); returns nothing.
Can someone point out what's the wrong thing I am doing? Can it be done? If not,what should I reference for a complete list of clauses where parameters can be used?
After using :
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
I got the message :
Uncaught exception 'PDOException' with
message 'SQLSTATE[42000]: Syntax error
or access violation: 1064 You have an
error in your SQL syntax; check the
manual that corresponds to your MySQL
server version for the right syntax to
use near ''0', '10'' at line 1
So, when you use an array for execute, it consider your inputs as string which is not a good idea for LIMIT
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "SELECT * FROM table ORDER BY :sort :dir LIMIT :start, :results";
$stmt = $dbh->prepare($sql);
$stmt->bindParam(':start', $_GET['start'], PDO::PARAM_INT);
$stmt->bindParam(':results', $_GET['results'], PDO::PARAM_INT);
$stmt->bindParam(':sort', $_GET['sort']);
$stmt->bindParam(':dir', $_GET['dir']);
$stmt->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($data);
Prepared statements allow the DBMS to generate a query plan for your query before actually executing the query for your supplied parameters. Changing the fields for ORDER BY requires a different query plan, because ordering you data in different ways can drastically affect how the DBMS might choose to get the data: for instance, certain indexes may help in one case but not in another. For this reason the ORDER BY fields should form part of the SQL string passed into the prepare() method, rather than being bound to the query prior to execute().
As for the LIMIT clause, it's not clear whether its parameters would affect the query plan, so these may be bound later, possibly depending upon your DBMS. According to this SO answer it should be allowed.
You can't bind a parameter to specify a language keyword or a field name - it has to be replacing a literal. Therefore, your limit values I think are fine, but your order by is not. It will be best for you to manually replace sort and dir in the string. Escape them but don't use the DB tools to do so, since they aren't string literals. Basically ensure no special characters are present.
Although this question is rather old, I think it might still be of interest. For me it worked after I
used bindParam in combination with PDO::PARAM_INT like suggested before
converted the variable content into an integer value by invoking intval()
The relevant part of the code then looks like this:
$stmt->bindParam(':start', intval($_GET['start']), PDO::PARAM_INT);
$stmt->bindParam(':number', intval($_GET['number']), PDO::PARAM_INT);
Without using intval() I also received the error Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''0', 10' at line 1
I'm having a problem with a query prepared in PHP with PDO. The code:
$link = new PDO("mysql:dbname=$dbname;host=127.0.0.1",$username,$password);
$link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$query = $link->prepare("SELECT locality_name FROM :passedday GROUP BY locality_name ORDER BY locality_name DESC");
$query->bindParam(":passedday",$day); //Where day is, well, a day passed to the script elsewhere
$query->execute();
$result = $query->fetchAll();
$link = null;
//Do things with the $result.
The error message I am getting is:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''05_26_09' GROUP BY locality_name ORDER BY locality_name DESC' at line 1
When I execute the query on the server directly, it returns the appropriate result set without any problem. Any ideas what I'm doing wrong?
TIA.
Edit:
$day is passed as a GET argument. So, http://127.0.0.1/day.php?day=05_26_09 leads to $day = $_GET['day'];.
If 05_26_09 is supposed to bet the table's name, then I guess you've an escaping problem. Is your local operating system different from the live server?
I don't think you can use bindValue()/bindParam() for something else than values (eg. table name, field name). So I'm a bit suprised, that it works on your local system.
PDO uses mysql's C-API for prepared statements.
http://dev.mysql.com/doc/refman/5.0/en/mysql-stmt-prepare.html says:The markers are legal only in certain places in SQL statements. [...] However, they are not allowed for identifiers (such as table or column names)As a rule of thumb I use: "if you can't wrap it in single-quotes in an ad-hoc query string you can't parametrize it in a prepared statement"