Understanding PDO Prepared Statements and Binding Parameters - php

From experience and also having been told constantly the benefits of using prepared statements and binding my parameters, I have constantly used those two techniques in my code, however I would like to understand exactly the purpose of each of those two techiques:
From my understanding of prepared statements:
$sql = "SELECT * FROM myTable WHERE id = ".$id;
$stmt = $conn->prepare($sql);
$stmt->execute();
The previous code should create a sort of a buffer in the database with the query I proposed. Now FROM MY UNDERSTANDING (and I could be very wrong), the previous code is insecure, because the string $sql could be anything depending on what $id actually is, and if $id = 1; DROP TABLE myTable;--, I would be inserting a malicious query even though I have a prepared statement.
FROM MY UNDERSTANDING this is where binding my parameters com in. If I do the following instead:
$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':id', $id);
$stmt->execute();
The database should know exactly all the parts of the sql statement before hand:
SELECT these columns: * FROM myTable and WHERE id = "a variable that was input by the user", and if "a variable that was input by the user" != a variable, the query fails.
I have been told by some my understanding is correct, and by others that it is false, could someone please let me know if I am wrong, correct, or missing something? And elaborate as much as you want, all feedback is greatly appreciated!

You're correct that the first case is insecure. It's important to understand though, that preparing a statement only has value if you are using variable data, and/or executing the same query repeatedly. If you are executing plain statements with no variables, you could simply do this:
$sql = "SELECT * from myTable WHERE this_column IS NOT NULL";
$result = $conn->query($sql);
And end up with a PDOStatement object to work with, just like when you use PDO::exec().
For your second case, again, you're largely correct. What's happening is the variable passed to the database is escaped and quoted (unless you specify otherwise with the third argument to PDOStatement::bindParam(), it's sent as a string which is fine for most cases.) So, the query won't "fail" if bad data is sent. It behaves exactly as if you had passed a valid number that didn't exist as an ID in the database. There are, of course, some edge cases where you are still vulnerable even with a correctly prepared statement.
Also, to make life easier, you can use prepared statements like this, to do implicit binding:
$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->execute([":id"=>$id]);
Or even like this, with un-named parameters:
$sql = "SELECT * FROM myTable WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->execute([$id]);
Naturally, most of this has been explained in the comments while I was typing up the answer!

Related

PHP PDO mysql prepared statment and join

I have a question.
I have the following query:
$query = "select * from module,bloc where module.id_bloc = ?";
I tried to bind the value so I did:
$stmt = $this->db->prepare($query);
$stmt->bindValue(1, "bloc.id_bloc");
But, when I test I don't get any result on my browser.
It's weird because when I replace directly inside like the following code:
$query = "select * from module,bloc where module.id_bloc = bloc.id_bloc";
I get the the right result on my browser.
Could someone explain to me why it doesn't work when I am doing a bindValue?
It will not work because, when bound, a string will be quoted. (Or, for all intents and purposes, work as if it were quoted, however PDO may handle it behind the scenes.) Then, your query is interpreted as:
select * from module,bloc where module.id_bloc = 'bloc.id_bloc'
That is: It will be interpreted as a literal string, rather than a reference to a table column, and will obviously not give you the expected result. There is no need for binding it to begin with.
If, for some reason, you need to run a query with a variable table/column name from an unsafe source, you will have to manually format/sanitize it; see here for an example of how to do it.

MySQL Injection Confusion, How Does it Actually Protect?

I've been reading about MySQL Injection as I've just started adding prepared statements to my site. I wanted to know a bit more about how it actually protects and after reading some, I don't think I've done it correctly.
I was reading from here: http://www.tizag.com/mysqlTutorial/mysql-php-sql-injection.php
I really don't understand how it can be 'injected' or how prepared statements get around it. Particularly this bit:
Normal: SELECT * FROM customers WHERE username = 'timmy'
Injection: SELECT * FROM customers WHERE username = '' OR 1''
When using SELECT, the things I use for WHERE are only ID or username. They can't change their ID and the username is validated when they sign up by this:
function protect($string) {
global $con;
return mysqli_real_escape_string($con,strip_tags(addslashes($string)));
}
So is this "protect()" preventing MySQL injection?
If so, what are the uses for prepared statements? Is this protection correct?
Instead of
$car = 'nissan';
$update_car = mysqli_query($con,"UPDATE stats SET stats.car = $car WHERE stats.id = 4");
I put:
$car = 'nissan';
$query = "UPDATE stats SET stats.car = $car WHERE stats.id = 4";
$update_car = mysqli_query($con,$query);
If that is correct, I don't understand how that does anything besides just adding another line of code?
EDIT
First prepared statement, any good?
$getusername = mysqli_prepare($con, "SELECT users WHERE users.username = '",mysqli_real_escape_string($username),"'");
Firstly, let's look at your attempt at escaping:
function protect($string) {
global $con;
return mysqli_real_escape_string($con,strip_tags(addslashes($string)));
}
There are 2 mistakes here: Firstly, addslashes is doing the same job as mysqli_real_escape_string (but doing it badly), so remove that.
Secondly, you should always sanitise for the context you're working in. So, when generating SQL, you sanitise for SQL; when generating HTML, sanitise for HTML. So putting strip_tags in here doesn't make sense, because it's to do with HTML, but this is apparently an SQL escaping function. Do that separately, when you're preparing output in your templates.
In short, just mysqli_real_escape_string on its own would be better than what you have here.
Next, let's look at parameterising the query:
$car = 'nissan';
$query = "UPDATE stats SET stats.car = '$car' WHERE stats.id = 4";
$update_car = mysqli_query($con,$query);
This statement isn't prepared or parameterised - as far as the database is concerned, it's still just a string of SQL. If $car is actually set from user input (e.g. $car = $_GET['car'];), then you can insert any piece of SQL in the middle of the query:
$car = "nissan'; DROP TABLE stats; --";
$query = "UPDATE stats SET stats.car = '$car' WHERE stats.id = 4";
$update_car = mysqli_query($con,$query);
// MySQL will now drop your table; oops!
// UPDATE stats SET stats.car = 'nissan'; DROP TABLE stats; --' WHERE stats.id = 4
Adding $car = mysqli_real_escape_string($car); will correctly escape the ' in the input, stopping it ending the string and starting a new SQL statement.
Parameterised queries avoid the problem a different way: they tell the database which parts of the query are supposed to be SQL, and which are just data provided by the user. It looks like this:
$car = "nissan'; DROP TABLE stats; --";
$query_with_placeholder = "UPDATE stats SET stats.car = ? WHERE stats.id = 4";
// Note the ?, without quotes, represents somewhere for data to be put by MySQL
$prepared_statement = mysqli_prepare($con, $query_with_placeholder);
// Now we tell MySQL what to put in the placeholder
mysqli_stmt_bind_param($prepared_statement, 's', $car);
// And execute it
mysqli_stmt_execute($prepared_statement);
Because $car is never actually inserted into the SQL, just passed as a separate parameter, there is no way of it injecting anything nasty, and we don't need to do any additional escaping.
Also note that mysqli_real_escape_string is to prevent injection with strings. If you have a (for example) integer field then escaping the passed variable to cope with a quote doesn't really help as injection doesn't require the quote to work.
For example:-
$some_field_expected_to_be_int = '1 OR 1=1';
$query = "SELECT * FROM users WHERE id = ".mysqli_real_escape_string($con, $some_field_expected_to_be_int);
would still give a piece of SQL reading:-
SELECT * FROM users WHERE id = 1 OR 1=1
and return everything.
In such a case you should make sure it is an integer:-
$some_field_expected_to_be_int = '1 OR 1=1';
$query = "SELECT * FROM users WHERE id = ".(int)$some_field_expected_to_be_int;
Personally both prepared statements and escaping fields have their places. Prepared statements make it harder to forget to escape items and with many databases they give a performance advantage, but the mysqli placeholders are not readable, getting a statement out for debugging is not workable and it becomes a nightmare when used for heavily dynamic sql. But escaping leaves you with the risk you will forget somewhere and the code can look messy.
EDIT - to add a bit on prepared statements.
We use a couple of in house database classes which separate things a bit from mysql / mysqli / pdo / sql server, but for a basic example of using prepare and a bound parameter:-
<?php
if($stmt = $this->db->prepare("SELECT users WHERE users.username = ? "))
{
$stmt->bind_param('s', $username);
$stmt->execute();
}
?>
In this the SQL is not built up using the variable (which could have been manipulated), rather the variable is passed as a parameter.
Escaping the data and using prepared queries both protect against SQL injection. But escaping is more error-prone, because it's easy to forget to call it in all the places you need it. If you use prepared statements, the protection happens automatically.
There's no significant difference between
$update_car = mysqli_query($con,"UPDATE stats SET stats.car = $car WHERE stats.id = 4");
and
$query = "UPDATE stats SET stats.car = $car WHERE stats.id = 4";
$update_car = mysqli_query($con,$query);
This is just a stylistic choice. The second version can be useful when you want to insert debugging statements, e.g.
$query = "UPDATE stats SET stats.car = $car WHERE stats.id = 4";
echo $query;
$update_car = mysqli_query($con,$query);
Popular practice is to both sanitize input and use prepared statements with your queries whenever you have a possible user input variable.
Sanitation should also prevent users from overflowing your variables and upload large quantities of data to your application.
Preparing statements is your second measurement of security, it prevents mistakes you might make later, like forgetting to escape input or not realising what is actually user input next time you refactor your application.
Any time you don't properly guard against injection, you open your project up to users deleting data, modifying data or uploading malicious code to your application. (Do you really want someone to upload javascript in a textfield?)

Prepare statement not bindindg with bindvalue

I just recently started using PDO, not that much experience with it, but I recently crashed into an annoying problem.
I am trying to use bindValue with a fetch statement to retrieve informatie from a database. The variables I get are from a GET, from a previous page. Everything is going wel except for the fact that it is not assigning, I guess, a right value to one of the bindValue.
PDO:
$stmt = $dbconnect->prepare('SELECT * FROM :table WHERE id=:id');
$stmt->bindValue(':table', $table);
$stmt->bindValue(':id', $id);
$stmt->execute();
$row = $stmt->fetch();
I know the difference between bindValue and bindParam. The code is working fine when I hardcode the table value. I have been banging my head against a wall for a short hour now but I can't seem to figure it out. Could anyone beside giving me the correct syntax please explain what went wrong with my thinking because at this point I cannot think of a reason, besides maybe the misinterpretation of the string value, why this is going wrong.
Also for in the future I would like to know the precise content of the SQL command. I tried doing this:
$SQL = 'SELECT * FROM :table WHERE id=:id';
$stmt = $dbconnect->prepare($SQL);
$stmt->bindValue(':table', $table);
$stmt->bindValue(':id', $id);
$stmt->execute();
$row = $stmt->fetch();
But this won't bind the variable values to the SQL variable. Your help is much appreciated!
EDIT:
I noticed my post is a duplicate from a FAQ post: FAQ. So my question has been answered however my insight in PDO is not enough to undertand it. Could anyone please explain what happens with the next line of code and why this works, opbtained from the posted link!
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
Answer
Thanks to silkfire I came up with fix. Before inserting the SQL string just add the string content into the SQL string:
$SQL = 'SELECT * FROM '.$table.' WHERE id=:id';
PDO does not allow table names or column names to be placeholders. Just create the query with concatenation instead, but make sure the user supplies only valid values. This should be safe.

Safety of PDO param bindings

I am using parametrized queries with PDO to retrieve data from a MySQL database with a fairly complicated query. As a part of doing this, I use some code similar to this:
The query:
$query = "SELECT * FROM table WHERE something = :id_$id";
$stmt = $db->prepare($query);
The param binding:
$stmt->bindParam(":id_$id", $id);
My understanding is that PDO sanitizes your parametrized input by 'cleaning' the replacement string in the call to bindParam, but my question is:
Could an attacker exploit a construct like the above (via the value of $id) in order to inject undesirable SQL?
PDO does a textual replacement on the :id_$id with the sanitized value of $id, so I would think that no part of :id_$id (no matter what it ends up being) should end up in the final query, but I would love to get a definite answer!
Edit: It looks like I wasn't as clear as I should have been in explaining why I believe this could be a safe thing to do. Of course, I don't suggest that this is a good way to do things.
The reason I think this could be safe is that PDO (correct me if this is wrong) does a textual replacement of the sanitized bound-param on the replacement text. Intuitively, this should indicate that the replacement text (":id_$id") can be any value, since it will be entirely replaced by PDO when the parameter is placed in the query. Since the parameter replacing involves sanitizing the value of the parameter, while ":id_$id" may be dangerous to execute, "$id" (which is what appears in the final query) should be safe.
That's my reasoning, anyways. I'm not doing anything this dangerous in my code, so this is more of an academic interest.
Of course it's vulnerable.
However, using named placeholders is entirely optional. So, you don't have to use them at all:
$query = "SELECT * FROM table WHERE something = ?";
$stmt = $db->prepare($query);
$stmt->execute(array($id));
And, you know, whatever fairly complicated code can be simplified.
pdo does sanitize the input, you can further specify types in the third argument of bindValue/bindParam. I'd avoid using $ as part of your token as it could be interpreted by php if in "":
$query = "SELECT * FROM table WHERE something = :id;";
$stmt = $db->prepare($query);
$stmt->bindParam(":id", $id, PDO::PARAM_INT);
this would ensure that if $id is not an int pdo should raise an exception.
Even attempting to inject sql in this manner is escaped.
You should consider using a closed set of allowed values for $id. I mean:
switch ($id) {
case "value01":
$param = ":id_01";
break;
case "value02":
$param = ":id_02";
break;
default:
// safe value
$param = ":id_00";
}
$query = "SELECT * FROM table WHERE something = $param";
$stmt = $db->prepare($query);
$stmt->bindParam("$param", $id);

Is this kind of PDO query protected against SQL injection? [duplicate]

This question already has answers here:
How can prepared statements protect from SQL injection attacks?
(10 answers)
Closed 9 years ago.
First of all, yes, I've seen this Are PDO prepared statements sufficient to prevent SQL injection?, but it's not enough to answer my question, as I'm doing it differently.
Let's say I have code like this:
$userid = $_SESSION['data']['id'];
$query = "SELECT * FROM table WHERE userid='$userid'";
$action = $db->prepare($query);
$action->execute();
It's technically safe, as user can't affect to $userid, right? Say if I'm wrong on this.
But what if the code looks like this:
$userid = $_GET['id'];
$query = "SELECT * FROM table WHERE userid='$userid'";
$action = $db->prepare($query);
$action->execute();
Is it safe anymore? I'm unable to find any documentation about this that gives me black on white.
even if you have used PDO, your code is still vulnerable with SQL INjection because you have not parameterized the query, query must be parameterized in order for the values to be cleaned.
$userid = $_GET['id'];
$query = "SELECT * FROM table WHERE userid=?";
$db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
$action = $db->prepare($query);
$action->bindParam(1, $userid);
$action->execute();
The second statement isn't safe.
Instead, you should do something like
$stmt = $db->prepare('SELECT * FROM table WHERE userid=:id');
$stmt->bindParam(':id', $userid);
$stmt->execute();
Source
It's technically safe, as user can't affect to $userid, right? Say if I'm wrong on this.
You are wrong with that. Session data is outside data and must be treated with caution. This is because of:
SessionID and SessionName are given with the request directly. These values can be easily manipulated so that some different data is being put into the memory of your application.
Persistence. Session data can be modified in the persistence layer so it qualifies as input data always (!).
You are likely expecting an integer value, so make it one:
$userid = (int) $_SESSION['data']['id'];
Especially as you directly substitute the variable into your SQL query.
In the future don't think if it is safe. Consider to do things in a safe manner so that even if you missed something in another layer (like input through session) don't breaks the data-flow in your application.
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
...
$userid = (int) $_SESSION['data']['id'];
...
$query = "SELECT column FROM table WHERE userid = ?";
$stmt = $pdo->prepare($query);
$stmt->bindParam(1, $userid);
$stmt->execute();

Categories