PDO Binding Limit - php

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

Related

mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement

I’ve been trying to code a login form in PHP using a prepared statement but every time I try to log in I get the following error:
mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement
Here is my code:
<?php
$mysqli = new mysqli("localhost", "root" , "" , "security");
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password, $username);
$stmt->fetch();
Can someone tell me why this is happening?
$mysqli->prepare("SELECT username, password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s' ,$username);
$stmt->execute();
$stmt->bind_result($username, $password);
Your select syntax was wrong, the correct syntax is
SELECT field1, field2, field3 FROM TABLE WHERE field1 = ? AND field2 = ?
To select more fields simply separate them by a comma and not an AND
Explanation
The error message clearly states that the number of columns you are SELECTing does not match the number of variables you provided to mysqli_stmt::bind_result(). They need to match exactly.
For example:
-- ↓ 1 ↓ 2 ↓ 3
SELECT col1, col2, col3 FROM tableA
There are 3 columns being fetched, so you need to provide 3 variables.
$stmt->bind_result($var1, $var2, $var3);
There could be a number of reasons why the column count doesn't match variable count.
Count your columns and variables
The simplest cause is that you made a mistake in the count. Do a recount of both. Maybe you changed the SQL but forgot to adjust bind_result()?
SELECT *
Using SELECT * is not recommended with bind_result(). The number of columns in the table could change as a result of schema changes or joins and will break your application. Always list all the columns explicitly!
Logical problem with SQL
The code from the question contains a logical mistake. SELECT username AND password produces a single column in the result. The AND keyword evaluates to a boolean expression. To select multiple columns you must use ,. Maybe there is another logical error in the query that causes the SQL to produce a different number of columns than you expected?
UPDATE and INSERT
DML statements such as INSERT, UPDATE and DELETE do not produce result sets. You can't bind variables to such prepared statement. You need to execute another SELECT statement to fetch the data.
Fetching an array from the prepared statement
The return value of mysqli_stmt::bind_result() is not an array, it's just a boolean. If you expected this function to return an array, then you are probably looking for get_result() with fetch_all() instead.
To select an array you need to get the mysqli_result object first.
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$stmt->bind_param('s', $username);
$stmt->execute();
$mysqli_result = $stmt->get_result();
// The object can then be iterated or used with fetch_* methods
foreach($mysqli_result as $row) {
}
// or
$arrayRow = $mysqli_result->fetch_assoc();
If this function doesn't exist in your PHP installation, then it means you have PHP not installed properly. You need to either recompile it, or enable mysqlnd (e.g. in cPanel).
If you are only learning PHP, it would be much easier for you to learn PDO instead.

MySQL PDO query syntax error with ? parameters

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!

PHP Prepared MySQL statments have started adding quote marks

So I have been using prepared statements for a while and for a number of projects and it has been a really good clean way to interact with the MySQL db, but today I have come across a strange problems.
My prepared statement has started adding extra ' to the sql statements and for the life of me I have no idea why...
so here is the code:
<?php
$sortby="ORDER BY submit_date DESC";
$offset = 3;
$sql = "SELECT img_id, img_name, submit_date FROM tbl_images WHERE img_active='y' :sortby LIMIT :offset, 9";
$stmt = $this->_db->prepare($sql);
$stmt->bindParam(":sortby", $sortby, PDO::PARAM_STR);
$stmt->bindParam(":offset", $offset, PDO::PARAM_INT);
$stmt->execute();
?>
so the above doesnt return anything, so looking at the database logs, this is what the query looks like
SELECT img_id, img_name, submit_date FROM tbl_images WHERE img_active='y' 'ORDER BY submit_date DESC' LIMIT 3, 9
it seems to have put an extra set of ' ' around the "ORDER BY submit_date DESC", but yet hasnt around the offset?
Can anyone spot the problem as its driving me mad :)
Thank you in advance!
Solution, thanks to the guys that posted, you were correct, I split the fields out to parts and works like a charm. Code solution below:
<?php
$sortfield="submit_date";
$sortway="DESC"
$offset = 3;
$sql = "SELECT img_id, img_name, submit_date FROM tbl_images WHERE img_active='y' ORDER BY :sortfield :sortway LIMIT :offset, 9";
$stmt = $this->_db->prepare($sql);
$stmt->bindParam(":sortfield", $sortfield, PDO::PARAM_STR);
$stmt->bindParam(":sortway", $sortway, PDO::PARAM_STR);
$stmt->bindParam(":offset", $offset, PDO::PARAM_INT);
$stmt->execute();
?>
Have a look at the documentation for mysqli_stmt::prepare:
The markers are legal only in certain places in SQL statements. For example, they are allowed in the VALUES() list of an INSERT statement (to specify column values for a row), or in a comparison with a column in a WHERE clause to specify a comparison value.
Basically, anything structural to the query is not allowed to be a bound parameter. Only data can be sent in this way.
PDO's prepared statements work in effectively the same way. In your case, however, PDO is a bit stupid, because it's running in "emulate prepares" mode (which is the default, but you should turn it off to get the most from PDO). It basically does all the substitution itself, rather than sending the query and the data to the server separately. It sees that the data is a string and thinks "aha, a string: I need to put quotes around this." You therefore end up with your malformed query.
The solution is not to build up structural parts of your query with bound parameters. Either substitute them in yourself with concatenation, or (and this is better) have alternative query strings for different settings. This is the most secure way: anything involving concatenation is a recipe for insecurity.
Oh, and turn PDO emulate prepares off!
With PDO you shouldn't use prepared statement binding variable substitution outside the WHERE clause or an ON clause. If you do they--any string--will get quoted (as they should). While the $offset integer binding might work, you shouldn't do it. You should just substitute the value with a string (after comparing it to a whitelist array of valid values).
$sql = "SELECT img_id, img_name, submit_date FROM tbl_images WHERE img_active='y' $sortby LIMIT $offset, 9";
You want to string interpolate $sortby, rather than bind it as an escaped and quoted SQL literal.
(But take care not to interpolate untrusted SQL fragments!)
Parameter binding is for the substitution of literal values into queries, by which we usually mean plain numbers or strings. Parameters are not for SQL identifiers (like table or column names) nor for syntactic elements.
PDO is interpreting $sortby as a literal string, which is what you asked it to do:
SELECT ... WHERE image_active='y' 'literal string substituted here' ...
You're certainly generating a syntax error with that query.
Confusing matters somewhat is that MySQL does allow placeholders for arguments to LIMIT clauses. This is quite convenient, but surprising to those familiar with other RDBMSs.

How will this affect chances of preventing SQL injection?

I have posted about this before but never in this regard so please take a look:
I was told one way to do sql injections was to use 1=1 where someone can see all entries that don't belong to them.
But lets say i structure my query so that it also selects the user_id of the current user, would that work:
$userid = Current users stored id in database;
$postid = mysql_real_escape_string($_GET['id']);
And now lets assume that i enter: domain.com/page.php?id='' OR '1'='1'
Select article_name from table where user_id=$userid and post_id=$postid
Will the query still return everything or will it not since i have added the User_id barrier?
If you use PDO you don't have to worry about escaping data (in this situation):
$stmt = $pdo->prepare('SELECT article_name FROM table WHERE user_id = :userid AND post_id = :postid');
$stmt->execute(array(
':userid' => $userid,
':postid' => intval($_GET['id']) //Just to be safe
));
// You could also do this instead (thanks #Digital Precision)
//$stmt->bindValue(':postid', $_GET['id'], PDO::PARAM_INT);
//$stmt->execute(array(':userid' => $userid));
while($row = $stmt->fetch()) {
//Work with data
}
For more on PDO see the PHP docs.
The problem with using mysql_real_escape_string() is that as its name suggests it only escapes strings. It escapes the characters that can be used to terminate a string so that an attacker can't close a string and enter malicious SQL.
If you are stubborn and refuse to use PDO, you could use a function like intval() on any unsanitized integers to ensure they contain only numbers.
$post_id = intval($_GET['id']); //Now $post_id can only be a number
mysql_real_escape_string() is for sanitizing strings only. It does NOT protect from SQL injection in integers that are not wrapped in quotes, so your observation is correct: What is shown above is indeed not safe despite mysql_real_escape_string().
You need to either wrap your values in quotes:
Select article_name from table where user_id='$userid' and post_id='$postid'
or make sure that $userid and $postid are integers before running the query.
Not sure what you mean by "I was told one way to do sql injections was to use 1=1 where someone can see all entries that don't belong to them".
1=1 always evaluates to true. I've only ever seen this done when the query being generated by the application has only conditional where clauses with no root where clause. Not sure what it has to do with protecting you from sql injections.
Your query would look like:
Select article_name from table where user_id=$userid and post_id=\'\' OR \'1\'=\'1\'
As other mention while i typing this, it is better to quote your values. So you will have:
Select article_name from table where user_id=$userid and post_id='\'\' OR \'1\'=\'1\''
This returns nothing, if there is not a post with such id.
So your query will not return every post from the current user. But keep in mind to quote your values.

How do I use pdo's prepared statement for order by and limit clauses?

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

Categories