SQLSTATE[HY093]: Invalid Parameter number - PDO Select statement - php

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.

Related

Changing from mysqli to pdo

I do not have a problem with this code it surprisingly works fine but I really don't understand how it works or even is it right, so:
My queries with mysqli for ajax post or get calls were like this:
$con = mysqli_connect('localhost','root','','db') or die(header('Location: ./404.php'));
$add = "INSERT INTO table (id, id2, id3) VALUES('','$fid','')";
if(mysqli_query($con, $add)){
echo "added";
}
$remove = "DELETE FROM table WHERE id2='$fid'";
if(mysqli_query($con, $remove)){
echo "removed";
}
$getInfo = "SELECT * FROM table";
$result = $con->query($getInfo);
if($result->num_rows > 0) {
while($row = $result->fetch_assoc()){
//do something
}
}
And for $_POST or $_GET values I used mysqli_real_escape_string
Here is converted to PDO:
try{
$con = new PDO('mysql:host=localhost;dbname=db;charset=utf8mb4', 'root', '', array(PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
$get = $con->query("SELECT * FROM table");
foreach($get->fetchAll(PDO::FETCH_ASSOC) as $row){
$data['0'] = $row['name'];
$data['1'] = $row['email'];
return $data;
}
$add = $con->prepare("INSERT INTO table (id, id2, id3) VALUES(:f1,:f2,:f3)");
$add->execute(array(':f1' => '', ':f2' => $fid, ':f3' => ''));
echo "added";
$remove = $con->prepare("DELETE FROM table WHERE id2=:f1");
$remove->bindValue(':f1', $fid, PDO::PARAM_STR);
$remove->execute();
echo "removed";
}catch(PDOException $ex){
echo "error";
}
Now this works but I don't really know is it properly written with pdo where I don't need to use something like mysqli_real_escape_string with mysqli or something like that.
And all I could find on web is that how it is written now what part of code is doing what for example when I use in mysqli insert, update or delete I use
if(mysqli_query($con, $sql)){echo "success";}else echo 'fail';
How can I do that with pdo?
And also for using try and catch I don't know do I need to use it for every query or as I added above?
Just to say again I am new to pdo, I don't understand it very well and this code from above works but I do not know is it written the right way?
First off, let me congratulate you for going with PDO. Out of all the experienced PHP developers I know, it's near unanimous that they prefer PDO to mysqli.
I highly recommend you read through this guide to using PDO. It should answer all your questions and even answer a few you will likely have in the future.
To your specific questions:
No you do not need to escape anything anymore, so long as you are using prepared statements with placeholders. Escaping existed exactly because people were interpolating variables into SQL statements and that could confuse the quoting you needed to enclose strings.
With prepared statements that issue no longer exists, which also means that there is no longer the danger of SQL injection. SQL injection takes advantage of string concatenation to transform the original SQL statement into an entirely different one, again using quotes, which is why a non-escaped string accepted from user input was the attack vector for SQL injection. Both problems are solved using parameters and prepared statements.
As for error handling with PDO, you want to utilize PDO::ERRMODE_EXCEPTION which is discussed in the manual here.
Unfortunately, the default for PDO is PDO::ERRMODE_SILENT which essentially ignores database errors and just sets PDO object variables you would have to check yourself.
With that said, you can fix this by adding the error mode when you create the PDO connection object or just afterwards. Examples are on the PDO error mode page I linked.
As for Try-Catch blocks, in general an exception is not something you want to catch specifically unless you have some functional code to work around the error. Wrapping every sql call just so you can report an error message is bad, both from the point of view of DRY as well as being an anti-pattern. With the proper error mode, SQL errors will throw exceptions that you can handle in your error handler, and in general are things you shouldn't be eating up and continuing on from.
Your error handler should be (in production) logging the error to disk/emailing a sysadmin or site owner, and displaying a professional looking non-specific error message informing the user of the problem and that should be happening for all exceptions.

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!

Cannot figure out why SQL DELETE FROM statement returns an error of "SQLSTATE[HY000]: General error: 25 bind or column index out of range "

So, here's the relevant code from my page. It connects to a sqlite3 database through PDO which I update through forms on the page. I have other sqlite statements, like INSERTS and UPDATES (that does use WHERE id=:id) that work no problem. This DELETE one does not, however. I do have all the code in a try catch block on my page (which is how I got the error, if you were wondeing) but I figured I can omit it here.
Thanks for the help!
<?php
$db = new PDO("sqlite:osuat.sqlite3");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$id = $_POST['id'];
$update = "DELETE FROM pages
WHERE id=:id";
$stmt = $db->prepare($update);
$stmt->bindParam(':id', $id);
$stmt->execute();
?>
Try adding PDO::PARAM_INT to the bind_param method, to make sure that the value being sent is an INT (which I'm assuming your ID field is) i.e.,
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
Echo the actual sql statement and die prior to actually running it. Then inspect and run the statement directly. I suspect $_POST['id'] doesn't contain what you think it does.
I finally figured it out. In my actual page, I have a bunch of if-else constructs in order to construct the correct $update string. I (wrongly) figured that I could just use bindParam() at the end without paying heed to how many bindParam()s each update statement would need. So, for my DELETE FROM pages WHERE id=:id, it was being supplied a whole bunch of other parameters only used in other $update strings, not just :id.
Its my fault for not including the entire source, I'm sure someone here would have caught it right away, but many thanks to duellsy, he/she led me on the right path looking for ways to log the actual SQL statement. In the end, using stmt->debugDumpParams(); helped me figure what I was doing wrong.
Try writing the DELETE command in one line.

Replacing mysql_* functions with PDO and prepared statements

I've always done the simple connection of mysql_connect, mysql_pconnect:
$db = mysql_pconnect('*host*', '*user*', '*pass*');
if (!$db) {
echo("<strong>Error:</strong> Could not connect to the database!");
exit;
}
mysql_select_db('*database*');
While using this I've always used the simple method to escape any data before making a query, whether that be INSERT, SELECT, UPDATE or DELETE by using mysql_real_escape_string
$name = $_POST['name'];
$name = mysql_real_escape_string($name);
$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());
Now I understand this is safe, to an extent!
It escapes dangerous characters; however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
So, I searched a little bit and found out about PDO, MySQLi and prepared statements. Yes, I may be late to the game but I've read many, many tutorials (tizag, W3C, blogs, Google searches) out there and not a single one has mentioned these. It seems very strange as to why, as just escaping user input really isn't secure and not good practice to say the least. Yes, I'm aware you could use Regex to tackle it, but still, I'm pretty sure that's not enough?
It is to my understanding that using PDO/prepared statements is a much safer way to store and retrieve data from a database when the variables are given by user input. The only trouble is, the switch over (especially after being very stuck in my ways/habits of previous coding) is a little difficult.
Right now I understand that to connect to my database using PDO I would use
$hostname = '*host*';
$username = '*user*';
$password = '*pass*';
$database = '*database*'
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
if ($dbh) {
echo 'Connected to database';
} else {
echo 'Could not connect to database';
}
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work. So I'm having to read/remember a load of new ones, but this is where I'm getting confused.
If I wanted to insert data from say a sign up/registration form, how would I go about doing this, but mainly how would I go about it securely? I assume this is where prepared statements come in, but by using them does this eliminate the need to use something like mysql_real_escape_string? I know that mysql_real_escape_string requires you to be connected to a database via mysql_connect/mysql_pconnect so now we aren't using either won't this function just produce an error?
I've seen different ways to approach the PDO method too, for example, I've seen :variable and ? as what I think are known as place holders (sorry if that is wrong).
But I think this is roughly the idea of what should be done to fetch a user from a database
$user_id = $_GET['id']; // For example from a URL query string
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
But then I'm stuck on a couple things, if the variable wasn't a number and was a string of text, you have to given a length after PDO:PARAM_STR if I'm not mistaken. But how can you give a set length if you're not sure on the value given from user in-putted data, it can vary each time? Either way, as far as I know to display the data you then do
$stmt->execute();
$result = $stmt->fetchAll();
// Either
foreach($result as $row) {
echo $row['user_id'].'<br />';
echo $row['user_name'].'<br />';
echo $row['user_email'];
}
// Or
foreach($result as $row) {
$user_id = $row['user_id'];
$user_name = $row['user_name'];
$user_email = $row['user_email'];
}
echo("".$user_id."<br />".$user_name."<br />".$user_email."");
Now, is this all safe?
If I am right, would inserting data be the same for example:
$username = $_POST['username'];
$email = $_POST['email'];
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->execute();
Would that work, and is that safe too? If it is right what value would I put in for the ?_LENGTH_?? Have I got this all completely wrong?
UPDATE
The replies I've had so far have been extremely helpful, can't thank you guys enough! Everyone has got a +1 for opening my eyes up to something a little different. It's difficult to choose the top answer, but I think Col. Shrapnel deserves it as everything is pretty much covered, even going into other arrays with custom libraries which I wasn't aware of!
But thanks to all of you:)
Thanks for the interesting question. Here you go:
It escapes dangerous characters,
Your concept is utterly wrong.
In fact "dangerous characters" is a myth, there are none.
And mysql_real_escape_string escaping but merely a string delimiters. From this definition you can conclude it's limitations - it works only for strings.
however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
You're mixing here everything.
Speaking of database,
for the strings it is NOT vulnerable. As long as your strings being quoted and escaped, they cannot "modify or delete data maliciously".*
for the other data typedata - yes, it's useless. But not because it is somewhat "unsafe" but just because of improper use.
As for the displaying data, I suppose it is offtopic in the PDO related question, as PDO has nothing to do with displaying data either.
escaping user input
^^^ Another delusion to be noted!
a user input has absolutely nothing to do with escaping. As you can learn from the former definition, you have to escape strings, not whatever "user input". So, again:
you have escape strings, no matter of their source
it is useless to escape other types of data, no matter of the source.
Got the point?
Now, I hope you understand the limitations of escaping as well as the "dangerous characters" misconception.
It is to my understanding that using PDO/prepared statements is a much safer
Not really.
In fact, there are four different query parts which we can add to it dynamically:
a string
a number
an identifier
a syntax keyword.
so, you can see that escaping covers only one issue. (but of course, if you treat numbers as strings (putting them in quotes), when applicable, you can make them safe as well)
while prepared statements cover - ugh - whole 2 isues! A big deal ;-)
For the other 2 issues see my earlier answer, In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work.
That is another, grave delusion of PHP users, a natural disaster, a catastrophe:
Even when utilizing old mysql driver, one should never use bare API functions in their code! One have to put them in some library function for the everyday usage! (Not as a some magic rite but just to make the code shorter, less repetitive, error-proof, more consistent and readable).
The same goes for the PDO as well!
Now on with your question again.
but by using them does this eliminate the need to use something like mysql_real_escape_string?
YES.
But I think this is roughly the idea of what should be done to fetch a user from a database
Not to fetch, but to add a whatever data to the query!
you have to given a length after PDO:PARAM_STR if I'm not mistaken
You can, but you don't have to.
Now, is this all safe?
In terms of database safety there are just no weak spots in this code. Nothing to secure here.
for the displaying security - just search this site for the XSS keyword.
Hope I shed some light on the matter.
BTW, for the long inserts you can make some use of the function I wrote someday, Insert/update helper function using PDO
However, I am not using prepared statements at the moment, as I prefer my home-brewed placeholders over them, utilizing a library I mentioned above. So, to counter the code posted by the riha below, it would be as short as these 2 lines:
$sql = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);
But of course you can have the same code using prepared statements as well.
* (yes I am aware of the Schiflett's scaring tales)
I never bother with bindParam() or param types or lengths.
I just pass an array of parameter values to execute(), like this:
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );
This is just as effective, and easier to code.
You may also be interested in my presentation SQL Injection Myths and Fallacies, or my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Yes, :something is a named placeholder in PDO, ? is an anonymous placeholder. They allow you to either bind values one by one or all at once.
So, basically that makes four options to provide your query with values.
One by one with bindValue()
This binds a concrete value to your placeholder as soon as you call it. You may even bind hard coded strings like bindValue(':something', 'foo') if desired.
Providing a parameter type is optional (but suggested). However, since the default is PDO::PARAM_STR, you only need to specify it when it is not a string. Also, PDO will take care of the length here - there is no length parameter.
$sql = '
SELECT *
FROM `users`
WHERE
`name` LIKE :name
AND `type` = :type
AND `active` = :active
';
$stm = $db->prepare($sql);
$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
$stm->bindValue(':active', 1, PDO::PARAM_INT);
$stm->execute();
...
I usually prefer this approach. I find it the cleanest and most flexible.
One by one with bindParam()
A variable is bound to your placeholder that will be read when the query is executed, NOT when bindParam() is called. That may or may not be what you want. It comes in handy when you want to repeatedly execute your query with different values.
$sql = 'SELECT * FROM `users` WHERE `id` = :id';
$stm = $db->prepare($sql);
$id = 0;
$stm->bindParam(':id', $id, PDO::PARAM_INT);
$userids = array(2, 7, 8, 9, 10);
foreach ($userids as $userid) {
$id = $userid;
$stm->execute();
...
}
You only prepare and bind once which safes CPU cycles. :)
All at once with named placeholders
You just drop in an array to execute(). Each key is a named placeholder in your query (see Bill Karwins answer). The order of the array is not important.
On a side note: With this approach you cannot provide PDO with data type hints (PDO::PARAM_INT etc.). AFAIK, PDO tries to guess.
All at once with anonymous placeholders
You also drop in an array to execute(), but it is numerically indexed (has no string keys). The values will replace your anonymous placeholders one by one in the order they appear in your query/array - first array value replaces first placeholder and so forth. See erm410's answer.
As with the array and named placeholders, you cannot provide data type hints.
What they have in common
All of those require you to bind/provide as much values as you have
placeholders. If you bind too many/few, PDO will eat your children.
You don't have to take care about escaping, PDO handles that. Prepared PDO statements are SQL injection safe by design. However, that's not true for exec() and query() - you should generally only use those two for hardcoded queries.
Also be aware that PDO throws exceptions. Those could reveal potentially sensitive information to the user. You should at least put your initial PDO setup in a try/catch block!
If you don't want it to throw Exceptions later on, you can set the error mode to warning.
try {
$db = new PDO(...);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
echo 'Oops, something went wrong with the database connection.';
}
To answer the length question, specifying it is optional unless the param you are binding is an OUT parameter from a stored procedure, so in most cases you can safely omit it.
As far as safety goes, escaping is done behind the scenes when you bind the parameters. This is possible because you had to create a database connection when you created the object. You are also protected from SQL injection attacks since by preparing the statement, you are telling your database the format of the statement before user input can get anywhere near to it. An example:
$id = '1; MALICIOUS second STATEMENT';
mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1
and the executes the
malicious second statement */
$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a
single statement with
a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second
STATEMENT' i.e. returns empty set. */
Thus, in terms of safety, your examples above seem fine.
Finally, I agree that binding parameters individually is tedious and is just as effectively done with an array passed to PDOStatement->execute() (see http://www.php.net/manual/en/pdostatement.execute.php).

PDO Not Inserting Into Database

Hopefully someone will be able to help me because I've been banging my head against the wall all night trying to solve this little problem.
I want to insert data into a database using PDO (which I am admittedly not the most knowledgeable about). I am using a statement that I have used many times in the past, but for some reason this time it's not working. The statement is as follows:
$userID = "Johnny5";
$sql = "INSERT INTO user_info(user_id) VALUES(:user-id)";
if($stmt = $this->_db->prepare($sql))
{
$stmt->bindParam(":user-id", $userID, PDO::PARAM_STR);
$stmt->execute();
$stmt->closeCursor();
return TRUE;
} else {
return FALSE;
}
But unfortunately this is always returning TRUE without ever entering anything into my database. I have tried every combination changes to the statement that I could think of, but I am still at a loss.
I hope someone out there can point out a really simple error that I have made.
Also, placing single quote marks around the parameter :user-id in the $sql string is the only way that I can get anything to appear into the database, but that obviously doesn't enter in any actual data into the database.
EDIT
I have also changed the PDO parameter types from PDO::PARAM_STR to PDO::PARAM_INT but have still had no luck.
After further investigation, execute() is returning FALSE.
Solution
Thanks to everyone for their guidance. #Nabeel was correct in saying not to use placeholders in PDO parameters.
Don't use dashes in your SQL statements.
Make this:
:user-id
as this:
:userid
$userId is a string in this code, yet you say it's in int by defining PDO::PARAM_INT. Try replacing it with PDO::PARAM_STRING or try setting $userId to a number(1 for example)
If something fails, then there most certainly was an error. First you need to find out which statement fails by checking their return values (always read about every command you use in the PHP Manual first).
If you've located which command failed, use PDOStatement->errorInfo to find out more about the concrete error.
That information should help you to solve your issue.
Update: And if you want to deal with Exceptions (as Pekka suggested): How to squeeze error message out of PDO?

Categories