I had the following piece of code with PDO prepared statements:
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=:val LIMIT 1');
$stmt->bindValue(":val", $value);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
This works fine. It sends the following query:
113 Query SELECT `myColumn1` FROM my_table WHERE `myColumn2`=":val" LIMIT 1
and it returns the correct value.
But it doesn't work if I change the first line to
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=":val" LIMIT 1');
or
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=':val' LIMIT 1');
The same query is sent, but PDO returns false.
Can anybody explain why?
From the page you quote:
The parameters to prepared statements don't need to be quoted; the driver automatically handles this.
The purpose of the quotation marks is to delimit string data from the rest of the query, since it cannot easily be separated (unlike numbers, which have an obvious format). Since using prepared statements means that query and data are passed separately, the quotes are unnecessary.
One of the advantages of prepared statements are that types are handled for you (sort of...). In other words, prepared statements allow MySQL (or whatever RDBMS) to decide how to handle data. When putting quotes, that would force it to be a string which doesn't make sense. If it's supposed to be a string, then the server will handle that.
Related
In my table i have query:
$sql="SELECT * FROM `jom_x1_organizatori`
WHERE Organizator='".$sta_dalje."' Order by `Struka`,`Zanimanje`";
$sta_dalje=$_POST["state_id"] from another table and value is:
ЈУ Гимназија са техничким школама Дервента
"ПРИМУС" Градишка
In case 1 working.
How to make query?
Firts of all: Never build the query by concatenating the query string with user input! If you do, then escape the input with the library's dedicated function (mysqli_real_escape_string for example). Without escaping you will open a potential security hole (called SQL injection).
"ПРИМУС" Градишка is not working because after concatenating, the query will be invalid. Now imagine, what happens, if I post the following input: '; DROP TABLE jom_x1_organizatori; --
Your query will be:
SELECT * FROM `jom_x1_organizatori`
WHERE Organizator=''; DROP TABLE jom_x1_organizatori; --' Order by `Struka`,`Zanimanje`
Whenever you can use prepared statements to bind parameters (and let the library to do the hard work), but always escape and validate your input (using prepared statements, escaping is done by the library)!
$sta_dalje = (sting)$_POST["state_id"]; // Do filtering, validating, force type convertation, etc!!
// Prepare the statement (note the question mark at the end: it represents the parameter)
$stmt = $mysqlLink->mysqli_prepare(
"SELECT * FROM `jom_x1_organizatori` WHERE Organizator = ?"
);
// Bind a string parameter to the first position
$stmt->bind_param("s", $sta_dalje);
For more info about prepared statements:
mysqli prepared statements
PDO prepared statements
Please note that the old mysql extension is deprecated, do not use it if not necessary!
Just a side note
Do not use SELECT * FROM, always list the columns. It prevents to query unnecessary data and your code will be more resistant to database changes, plus debugging will be a bit simplier task.
Use escape string
$sta_dalje = mysqli_real_escape_string($con, $_POST["state_id"]);
And your where condition can be simply
Organizator='$sta_dalje'
I have a database table with a few BIT(1) type columns. If I were to forget about prepared statements, I could easily do something like this:
UPDATE tablename SET
bit_column_1 = b'1',
bit_column_2 = b'0'
And this would work perfectly. However, no matter what I try, when using prepared statements, the value is always '1'.
I have done the following and none of them work if $_POST['bit_col'] is 0:
$stmt = $dbh->prepare("UPDATE tablename SET
bit_col = :bit_col ");
// First attempt
$stmt->bindValue('bit_col', $_POST['bit_col']);
// Second attempt
$stmt->bindValue('bit_col', $_POST['bit_col'], PDO::PARAM_INT);
// Third attempt
$stmt->bindValue('bit_col', "b'{$_POST['bit_col']}'");
Then I tried altering the prepared statement to put the b there, but I get number of bound variables does not match number of tokens
$stmt = $dbh->prepare("UPDATE tablename SET
bit_col = b:bit_col ");
$stmt->bindValue('bit_col', $_POST['bit_col']);
$stmt = $dbh->prepare("UPDATE tablename SET
bit_col = b':bit_col' ");
$stmt->bindValue('bit_col', $_POST['bit_col']);
Another thing worth mentioning is PDO::ATTR_EMULATE_PREPARES is set to true. Setting this to false would require me to refactor things quite a bit because of how I unknowingly managed database connections.
So my question is, is it possible to use prepared statements with BIT columns in MySQL, and if so, how?
PDO::ATTR_EMULATE_PREPARES causes PDO to interact with BIT columns slightly differently. If it is set to false, you just insert the value as normal and MySQL will do any conversion necessary behind the scenes:
$stmt = $dbh->prepare("UPDATE tablename SET
bit_col = ? ");
$stmt->bindValue(1, $_POST['bit_col']);
However, if PDO is emulating the prepared statements, you need to put a b in there somehow to indicate that it is a BIT type. If you put the b in the bound parameter, PDO will escape the single quotes and you will end up with something like 'b\'0\'' being sent to MySQL, which obviously won't work. The b therefore needs to be in the query, not in the bound parameter. Doing this with named parameters produces the "number of bound variables does not match number of tokens" error above because PDO does not recognize strings with a b followed by a : as a named parameter. However PDO does recognize it as a parameter when you use question mark parameter markers, like this:
$stmt = $dbh->prepare("UPDATE tablename SET
bit_col = b? ");
$stmt->bindValue(1, $_POST['bit_col']);
Since we need to end up with something like b'1' being sent to MySQL, using PDO::PARAM_INT when binding the value will cause the query to fail because it will become UPDATE tablename SET bit_col = b1 (without the quotes around the number) so you must leave the data type out or use PDO::PARAM_STR.
Also note that if emulating prepared statements is disabled, this query will fail with a syntax error, so unfortunately the queries need to be done completely differently depending on whether or not you are emulating prepares or not.
if I'm not mistaken, the syntax should be:
$stmt->bindValue(':bit_col', $_POST['bit_col']);
to reference the :bit_col from:
$stmt = $dbh->prepare("UPDATE tablename SET bit_col = :bit_col ");
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.
I do understand that the prepared statements is the ultimate way to seek protection against the SQL injection. However, they provide coverage in a limited fashion; for example, in cases where I let the user to decide how the order by operation to be ( i.e, is it ASC or DESC? etc ), I get no coverage there with the prepared statements.
I understand that I can map the user input to a pre-defined white list for that. But, this is only possible when a whitelist can be created or guessed thoroughly beforehand.
For example, in the cases I mention above ( the ASC, or DESC ), this can easily be mapped and verified against a list of accepted values. But isn't there a situation where the portion of the SQL statement cannot be verified against a white list?
If such a situation exists, then what's the recommended approach?
If I were to escape the user_input using the underlying database's built-in escape utility (such as mysqL_real_escape_string for mysql) across the board, where would I fail?
I'm asking this question with the assumption that I always construct my sql statements with quoted values - even for integers...
Let's take a look at the following example and reflect upon it..
select {$fields} from {$table} where Age='{$age}' order by {$orderby_pref}
Assume all vars are user supplied.
If I were to mysql_real_escape_string all the variables in the above SQL ( as opposed to using prepared statements which covers me only half-way forcing me to come up whitelists for the other half that it cannot help), wouldn't it be equally safe (and easier to code)? If not, in which input scenario escape utility would fail?
$fields = mysql_escape($fields);
$table = mysql_escape($table);
$age = mysql_escape($age);
$orderby_pref = mysql_escape($orderby_pref);
select {$fields} from {$table} where Age='{$age}' order by {$orderby_pref}
You always need to use white-lists for stuff like table- or column names, whether you use prepared statements or the mysql escape functions.
The problem is that table names and column names are not quoted in single or double quotes, so if you use a function that specifically quotes these characters (and some more of course...), it will do nothing for your table name.
Consider the table name my_table; DELETE * FROM mysql; SELECT * FROM my_table. Nothing in this string will get escaped by mysql's escape functions but it is definitely a string you would want to check against a white-list.
Apart from that the mysql escape functions have a problem with character sets that can render them useless, so you are always better off with prepared statements.
You could use PDO and your life will get easier ... :
# Order
switch(strtoupper($Order)){
default:
case 'ASC':
$Order = 'ASC';
break;
case 'DESC':
$Order = 'DESC';
break;
}
# ID
$ID = 39;
$Username = 'David';
# Query
$Query = $this->DB->Main->prepare('SELECT * FROM Table WHERE ID = :ID AND Username = :Username ORDER BY HellBob '.$Order);
$Query->bindValue(':ID', $ID, PDO::PARAM_INT);
$Query->bindValue(':Username', $Username, PDO::PARAM_STR);
# All good ?
if(!$Query->execute()){
exit('Error');
}
// Results
$Row = $Query->fetch(PDO::FETCH_ASSOC);
You don't have to worry about quotes or SQL injections. You can use simple "white list" as you mention to get variable into your query.
http://php.net/manual/en/pdo.prepared-statements.php
If an application exclusively uses prepared statements, the developer can be sure that no SQL injection will occur (however, if other portions of the query are being built up with unescaped input, SQL injection is still possible).
What are the possible scenarios where some of the input is unescaped? Is that even possible if all the other input goes into the database using PDO?
I'm thinking of the scenario where other input is processed with mysql_* functions and not escaped with mysql_real_escape_string. Is there anything else that could be a threat?
Thanks a lot. Regards
It means you cannot use untrusted values directly e.g. as a column or table name - or as a LIMIT parameter.
For example, this is safe:
$query = "SELECT * FROM tbl WHERE col = ?";
while these aren't:
$query = 'SELECT * FROM tbl WHERE col = ? LIMIT ' . $_GET['limit'];
$query = 'SELECT * FROM tbl WHERE ' . $_GET['field'] . ' = ?';
$query = "SELECT * FROM tbl WHERE col = ? AND othercol = '" . $_GET['other'] . "'";
$query = 'SELECT * FROM ' . $_GET['table'] . ' WHERE col = ?';
Basically, prepared statements' placeholders are meant to be used in places where you would have used an escaped value within single quotes in a classical query.
In case you wonder why databases usually do not support placeholders for things like table names: Besides the fact that dynamic table/column names are not that common, the database engine usually optimizes a prepared statement when it's prepared. This however cannot be done properly without knowing exactly which tables/columns are accessed.
Consider this:
$sql = "SELECT * FROM ".$_GET['tablename']." WHERE somecol = ?";
Because I populated the table name with un-escaped user input, it would be possible to pass in for example public_table p LEFT JOIN hidden_table h ON h.id = p.id and get results you didn't want me to, even though you have escaped the value passed to the somecol comparison.
The point is that while prepared statements safely escape any user input you pass to a ? in the query, they can't escape data that already existed in the string before you passed it to prepare().
It means don't be lured into thinking PDO is magic pill...if you don't use prepared statements, you will still be vulnerable.