Much thanks for the discussion my original question generated. I took Jay's suggestion to use bind_param(), but there is something I don't understand about it that may be giving me the Server Error: "The website encountered an error while retrieving...". I don't know what the parameter 'sssd' that came with the example means.
Any suggestions as to what is generating the Server Error are much appreciated.
<?php
$mysqli = new mysqli('my-database-address', 'my-username', 'my-password', 'my-database-name');
f (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit(); }
$stmt = $mysqli->prepare("INSERT INTO volunteers VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param('sssd', $first_name, $last_name, $street_address, $apt_unit, $city, $zip,
$email, $phone, $planning, $signatures, $canvassing, $phone_bank, $media, $press_releases,
$volunteer_coordinator, $speaker, $house_parties, $web_page, $other, $skills, $organizations);
$first_name = '$_POST[first_name]'; $last_name = '$_POST[last_name]'; $street_address = '$_POST[street_address]';
$apt_unit = '$_POST[apt_unit]'; $city = '$_POST[city]'; $zip = '$_POST[zip]'; $email = '$_POST[email]';
$phone = '$_POST[phone]'; $planning = '$_POST[planning]'; $signatures = '$_POST[signatures]';
$canvassing = '$_POST[canvassing]'; $phone_bank = '$_POST[phone_bank]'; $media = '$_POST[media]';
$press_releases = '$_POST[press_releases]'; $volunteer_coordinator = '$_POST[volunteer_coordinator]';
$speaker = '$_POST[speaker]'; $house_parties = '$_POST[house_parties]'; $web_page = '$_POST[web_page]';
$other = '$_POST[other]'; $skills = '$_POST[skills]'; $organizations = '$_POST[organizations]';
$stmt->execute();
$stmt->close();
echo "<br /><br />";
echo "<div class='center-col-wrap'>";
echo "Your information has been received.";
echo "<br /><br />";
echo "Thank you for volunteering!"; echo "<br />";
echo "Your help in this effort to bring greater democracy to Oakland"; echo "<br />";
echo "will go a long way to create a healthy and informed community."; echo "<br />";
echo "<br /><br />";
echo "<a href='http://communitydemocracyproject.org/'>Return to CDP Home Page.</a>";
echo "</div>";
$mysqli->close();
?>
MY ORIGINAL QUESTION IS BELOW:
I didn't know if this would work or not. It does not So how can I use htmlentities() here?
Any help is much appreciated.
$sql="INSERT INTO volunteers (first_name, last_name, street_address, apt_unit, city, zip, email, phone,
planning, signatures, canvassing, phone_bank, media, press_releases, volunteer_coordinator, speaker,
house_parties, web_page, other, skills, organizations)
VALUES
('htmlentities($_POST[first_name])','htmlentities($_POST[last_name])','htmlentities($_POST[street_address])',
'htmlentities($_POST[apt_unit])','htmlentities($_POST[city])','htmlentities($_POST[zip])',
'htmlentities($_POST[email])','htmlentities($_POST[phone])','$_POST[planning]','$_POST[signatures]','$_POST[canvassing]','$_POST[phone_bank]',
'$_POST[media]','$_POST[press_releases]','$_POST[volunteer_coordinator]','$_POST[speaker]',
'$_POST[house_parties]','$_POST[web_page]','$_POST[other]','htmlentities($_POST[skills])','htmlentities($_POST[organizations])')";
You never want to use htmlentities() on data that's going to be inserted into the database!
NEVER!
Only use it when you are outputting data to a web page.
Otherwise, if you ever want to do anything with that data, ever, that's not directly to a web browser (think, sending emails, or counting words, or outputting to CSV, what have you), you'll quickly realize your error.
As Theodore said, never use htmlentities() for "escaping" something you want to put in your DB.
I strongly recommend to use prepared statements when anything that (could) come from outside (the user) is stored in a database.
Prepared statements are really easy to use.
If you use PDO to access you database help can be found here.
As you can see the bindParam() method is used to assign any value to a placeholder in the query.
If you use mysqli you can find the docs here.
The syntax of bind_param() is slightly different since the placeholders don't have names (order matters) and first argument is a string that determines what type the arguments have ("s" for string, "i" for integer and so on).
Using prepared statements has several positive effects. First of all it automatically masks the data that is provided in the bindParam()/bind_param() method and is the best way to close the SQL injection attack vector and it even optimizes the performance of your queries by storing the execution plan in the database (this has a little overhead but if you execute a query twice it pays off double).
PS: htmlentities() should only be used if you want to display some HTML as raw text to your users (code listings for instance).
PPS: Don't use real_escape_string() to prevent SQL injection since it's not safe (supeskt.org)
Update
First of all, for a follow up you should ask a new question. People don't read questions that are already marked as answered and by opening new questions you give kind people an opportunity to get some reward. :)
Nevertheless, the first argument "sssd" tells the database provider that you are passing four arguments, three of type string and a fourth of type double (in the example in the docs three strings and one double are bound ("DEU", "Bavarian", "F" and 11.2)). This is obviously not the case here, you are actually passing (binding) 21 values.
Depending of the type that the columns in your volunteers table have you need to pass a string of 21 characters as the first argument.
There are four possible chars that can be used to determine the type:
i for integer
d for double (floating point numbers)
s for string
b for boolean
All you have to do is to check what types you DB columns have. You will see that the types in the database have different names (like varchar, float etc.). If you google for this names you will find out that they are similar to string, integer, double and boolean. So you have to choose the best matching type depending on the column type (string ≆ varchar, double ≆ float, string ≆ tinytext, string ≆ date/datetime etc. pp.) and you should ensure that the values (your $_POST variables) actually match the type you defined.
Assuming that all your columns are of an text like type like varchar, the first argument would look like 'sssssssssssssssssssss' (21 times s) or 'ssssssssssssssissssss' if the column that takes the volunteer_cordinator is of type int (just for instance).
After you have done this you should double check if f (mysqli_connect_errno()) is a copy&paste related mistake or if you have actually missed the i in your code (should be if (mysqli_connect_errno())).
If you have checked that you should consider to write $_POST['xyz'] instead of '$_POST[xyz]', it will help you, really (' marks the start/end of a string and xyz is in fact the string here).
If you still encounter errors enable more detailed error information by adding error_reporting(E_ALL); at the top of your file (you should remove this for security reasons when your site goes live) and ask a new question.
Update 2
Double check your MySQL connection string (the arguments you pass in the mysql() method). Are you sure that your password starts with an # and ends with a full stop? By the way, you shouldn't post passwords etc. in the public.
Ensure that you server supports the mysqli methods by running a script containing only
<?php
// Show all information, defaults to INFO_ALL
phpinfo();
?>
and check the output for something like this:
To answer your question exactly as you wanted, you'll need to exit out of your string:
$sql="INSERT INTO volunteers (...) VALUES
('".htmlentities($_POST['first_name'])."','".htmlentities($_POST['last_name'])'." ...
(But please, as Theodore clearly says, don't do this. It's bad. Really, don't do it. Please!)
I think you're trying to escape your input/output. The best way to do is is firstly to stop SQL injection, use your favourite DB escaping method. I'm just using this as an example, you might have a better setup than this short example code:
$sql="INSERT INTO volunteers (...) VALUES
('".$mysqli->real_escape_string($_POST['first_name'])."','".$mysqli->real_escape_string($_POST['last_name'])'." ...
And then when you output, escape using htmlentities:
echo htmlentities($output->first_name);
Related
The code below does exactly what it's expected to do. It adds a client into the database successfully. But I never told the query to execute or add a new client all I did was store the query in a variable and checked to see if it was valid in the if statement. I need some help understanding how the query executed.
$query = "INSERT INTO clients(id, name, email, phone, address, company, notes, date_added) VALUES(NULL, '$clientName', '$clientEmail', '$clientPhone', '$clientAddress', '$clientCompany', '$clientNotes', CURRENT_TIMESTAMP)";
$result = mysqli_query($connection, $query);
// if query was successful
if( $result ){
header("LOCATION: clients.php?update=success");
} else{
// something went wrong
echo "Error: $query <br>" . mysqli_error($connection);
}
The way you should be doing this is a little more self-explanatory:
// Prepare this query with placeholders for where the user data will go.
// This creates a prepared statement handle ($stmt)
$stmt = $connection->prepare("INSERT INTO clients(name, email, phone, address, company, notes, date_added)
VALUES(?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)");
// Bind the user data to the statement so it will be escaped properly
// and inserted correctly.
$stmt->bind_param(
"ssssss",
$clientName,
$clientEmail,
$clientPhone,
$clientAddress,
$clientCompany,
$clientNotes
);
// Execute the statement now that everthing is in place. This actually
// sends the query to the MySQL server to be executed and waits
// for the result. The result of this function call indicates success
// or failure.
if ($stmt->execute()) {
// Query was successful then `execute()` returns a logically true value
// and this block of code will run.
header("Location: clients.php?update=success");
} else {
// If that previous condition didn't trigger, then we end up here and
// this code will run instead.
echo "Error: $query <br>" . $connection->error;
}
If you have an AUTO_INCREMENT column don't specify it in your list of VALUES, you can omit it and it will be populated automatically. Any column with a NULL default can also be omitted. There's no point in force-inserting NULL if that's how it will end up anyway.
You also need to pay careful attention to how you insert your data. You cannot use string interpolation to do this, it's extremely dangerous. The bind_param method takes care of adding the data in a safe manner if you've created a prepared statement that has placeholder values (?). This all but guarantees your code will be safe, secure and free from escaping errors that can take a lot of time to identify and repair.
I've also switched this to use the object-oriented style of mysqli. Not only is this significantly less verbose, it also becomes more clear as to what the operation is being performed on. $stmt->function() is obviously something making use of or manipulating the $stmt object. If it's just one argument of many that can be harder to identify.
Specifying arguments directly to functions instead of leaning on these intermediate variables is also a good habit to get into. Things like $sql tend to clutter up your code and confuse the intent of that string, plus if you have several of them you're juggling, like $sql3 and $sql8 there's an opportunity to make a tiny typo that causes real problems.
First time posting to StackOverflow, but I've been a long time reader of the comments here.
I ran across a programming bug that I could not figure out after several hours, and couldn't find anything in the questions and responses that seemed to apply, so here goes my first post!
I'm using PHP and a MySQLi prepared statement to insert data that originates from a file into a database and stores each record from the file into an array ($dbData[]) that is passed to a function that performs the database storage. I have checked the array values for all four indices for each record and data exists in all record fields, although there is a lot of slashes (forward and backwards), apostrophes, quotes (single and double) and html tags in some of the string content.
The $dbData array correlates to the columns in the table for the insert.
The table is called "content" and it has a constraint on the second column that it cannot be null. It is named 'CText'.
The code that operates on the data array is in a function that is called within a loop for all of the file data. I am providing the contents of the function below without the function interface. For simplification purposes I have included code that connects to the database, but the code that actually creates the database connection resides outside the function.
$mysqli = new mysqli("example.com", "user", "password", "database");
...
$queryText = "insert into content values (?, ?, ?, ?)";
$query = mysqli->prepare($queryText);
$query->bind_param('dsds',$dbData[0],$dbData[1],$dbData[2],$dbData[3]);
if (!$query->execute()) {
echo '<br>Execute failed: (' . $query->errno . ') . $query->error;
echo '<br>dbData[1]: ' . $dbData[1];
}
The insert works for most of the $dbData record data, but I get this error on a few of the records:
Execute failed: (1048) Column 'CText' cannot be null
CText: {data printed here that is truncated after what appears to be a line return in the string content}
My question is whether the bind may have issues with certain characters, like a line feed/carriage return or some other combination. I have not set a character encoding in the code so the default encoding is in use.
whether the bind may have issues with certain characters, like a line feed/carriage return or some other combination.
No. There are no issues with bind. The only reason for such error message is when data IS null.
Here goes simple checklist for solving all the problems of the kind.
Trust your eyes. You are not a sole user of mysqli prepared statements. There are millions of them, and noone experienced such a problem yet. Means if database is reporting that value cannot be null, then value IS null.
Anyway, if you still thinks that there is a bug - create an isolated, reprobuceable code example. Means not just create unworkable sketch to show to the readers, but a complete working example that anyone can reproduce. Such as
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("CREATE TABLE content ...");
$queryText = "insert into content values (?, ?, ?, ?)";
$query = $mysqli->prepare($queryText);
$dbData[0] = 0;
$dbData[1] = 'some text that yo think is causing the error';
$dbData[2] = 0;
$dbData[3] = 'some another text';
$query->bind_param('dsds',$dbData[0],$dbData[1],$dbData[2],$dbData[3]);
if (!$query->execute()) {
echo '<br>Execute failed: (' . $query->errno . ') . $query->error;
echo '<br>dbData[1]: ' . $dbData[1];
}
without loops. without any code that is not shown - but a complete, working example that produces the noted error.
After failing with such an example, start debugging your code, to find a logical error either with program flow or the way you debug it.
You have to realize that this kind of questions ("I have a bug in PHP out of the clear sky") cannot have another receive without a reproduceable example.
I'm getting the error: Column count doesn't match value count at row 1
I think, normally this error occurs if the count of the columns and the values aren't equal, but in my code they are...(3).
This is my php code:
$tempsongtitel = $_POST['songtitle'];
$tempinterpret = $_POST['interpret'];
$templink = $_POST['link'];
$query = mysql_query("insert into tMusic (Songtitel, Interpret, Link) values ('$tempsongtitel, $tempinterpret, $templink')") or die(mysql_error());
You missed some quotes. Should be:
$query = mysql_query("insert into tMusic (Songtitel, Interpret, Link) values ('$tempsongtitel', '$tempinterpret', '$templink')") or die(mysql_error());
Otherwise, you were trying to insert all three POST values into the first field.
Moreover, the mysql_ extension has been deprecated and is on the way out and is highly discouraged, especially if you are creating new software.
AND I'll presume you are first sanitizing your data? You're not really taking user input and placing it directly into the database, are you? Even if you don't do any data validation, you should escape your data in the query... easiest and most foolproof way to do that is by using parameterized queries.
The root cause is that your values are all in one set of quotes instead of quoted individually. I think this is a pretty common error, and in my experience it is an easy mistake to make, but not immediately obvious when scanning over your code. You can fix it like this (quick fix, still using deprecated mysql, but with post values escaped):
$tempsongtitel = mysql_escape_string($_POST['songtitle']);
$tempinterpret = mysql_escape_string($_POST['interpret']);
$templink = mysql_escape_string($_POST['link']);
$query = mysql_query("insert into tMusic (Songtitel, Interpret, Link)
values ('$tempsongtitel', '$tempinterpret', '$templink')") or die(mysql_error());
If you can, it would be much better to update your code to use PDO. You could use a prepared statement like this:
$stmt = $pdo->prepare("INSERT INTO tMusic (Songtitel, Interpret, Link) VALUES (?, ?, ?)");
$stmt->bindValue(1, $tempsongtitel);
$stmt->bindValue(2, $tempinterpret);
$stmt->bindValue(3, $templink);
$stmt->execute();
Among the many benefits of using this database extension rather than the old mysql functions it should not be possible to make an error like this in your code. In the prepared statement, there are no quotes around the parameter markers, so if you have VALUES ('?, ?, ?'), or even VALUES ('?', '?', '?') You would get bind errors when trying to bind the values, and the problem would become apparent pretty quickly.
I've found that, even though it's not 100% necessary and it's more time consuming, properly quoting and backticking EVERYTHING helps prevent this from happening.
$myQuery = "INSERT INTO `tMusic` (
`Songtitel`,
`Interpret`,
`Link`
) VALUES (
'$tempsongtitel',
'$tempinterpret',
'$templink'
);";
$runQuery = mysqi_query($DBi, $myQuery) or die(mysqli_error($DBi));
The formatting you use is up to you but this helps me make sure I have a one to one relationship and that I've quoted everything.
Of course that's using mysqli_* in place of the deprecated mysql_* functions AND that's assuming you've set $tempsongtitel, $tempinterpret and $templink properly.
//The variables are declared outside this function. I use $_POST to retrieve each user input in another .php file using html form tag. The variable $db is my database connection.
function insertintodb ($db, $avar, $bvar, $cvar)
{
/*
How can I tell what values my variables are when using PDO bindParam? I output $avar to see its value in this function. How can I tell if PDO actually binded ":firstname" to $avar? Likewise with the other variables.
*/
echo 'before <br>';
echo $avar;
echo '<br>';
//firstname, midinitial, lastname are values in my database.
//name is my table I am inserting into.
$insertname = "INSERT INTO name (firstname, midinitial, lastname)
VALUES (:firstname, :midname, :lastname)";
echo 'before PDO prepare<br>';
echo $avar;
echo '<br>';
$stmt = $db->prepare($insertname);
$stmt->bindParam(':firstname', $avar);
$stmt->bindParam(':midname', $bvar);
$stmt->bindParam(':lastname', $cvar);
echo 'after binding variables using bindParam <br>';
echo $avar;
echo '<br>';
$stmt->execute();
echo 'after executing <br>';
echo $avar;
echo '<br>';
}
bindParam() returns true or false:
if($stmt->bindParam(':firstname', $avar)) {
echo 'Woo hoo yay!';
} else {
echo 'Boo hoo waa';
}
Just avoid bindParam at all. this will relieve you from burden of checking its result
function insertintodb ($db, $avar, $bvar, $cvar)
$sql = "INSERT INTO name (firstname, midinitial, lastname) VALUES (?, ?, ?)";
$stmt = $db->prepare($sql);
$data = array_slice(func_get_args()); // lets strip $db from the func args
$stmt->execute($data);
}
Trust PDO
If PDO has a bug, this is not your problem. It is PDO's - since it's fairly well tested, there are very few bugs in current PDO versions. That is, if you tell PDO to bind, then trust that it will bind (PDO will fail on execute if there are unbound parameters, so we don't even have to "trust" it too much).
However, use PDOStatement::bindValue (not bindParam, except in special cases) because bindValue will ensure the value supplied is bound and not merely a "reference [to the variable]". This prevents "accidental changes" to variables between binding and execution from affecting the query.
Write and Test a DAL
Write a Data-Access Layer (DAL)1, as opposed to inline spaghetti SQL, and then test it. While ensuring the parameter is "actually binded" sounds useful, it isn't doesn't ensure the code is valid semantically. For instance, what if the code incorrectly did $stmt->bindParam(':firstname', $lastname);?
Furthermore, PDO itself will fail (I recommend enabling Exceptions) on most basic "binding failures" (such as unbound parameters or nonconvertible values) when the query is executed, making the very nature of testing if a parameter is "actually binded" less important.
Since detecting binding is not relevant to determining the validity of the code, nor can PDO report exactly what data is stored due SQL conversion issues (including truncation), then the problem really isn't about checking each bind, it's about checking operations - and a DAL provides guaranteed contracts for different operations.
1 A DAL doesn't have to be scary, nor does it have to use "ORM" or "OOP" or "DI/IOC" or anything else (although an advanced DAL may use all of those "concepts"). Consider, for starters, a small handful of functions in a separately included file which are the only mechanism for connecting to the database and "talking to" SQL.
Each of these functions then has a simple contract (which as documentation on the top) which defines the parameters it takes, the results it returns, and any exceptions it may throw.
Congratulations, you've created a minimal and testable DAL!
Then this DAL, which is just a collection of functions for now, can be taken and tested/verified outside of the "actual program" (preferably using an existing test framework/harness).
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).