Is it possible to insert MySQL functions using bindValue()? My code below just gets outputted as the string 'NOW()' instead of the function NOW() (without quotes).
$sthandler->bindValue(1, 'NOW()');
No. A query parameter substitutes only for a single constant value. For example, a numeric constant or literal string or date.
Anything else -- column names, table names, SQL keywords, functions, expressions -- must be in the SQL string at parse time.
Re your comment:
You should understand that parameters are not just a convenience to interpolate extra strings into your SQL. PREPARE is analogous to a compile phase for Java or C#, whereas EXECUTE is analogous to running the compiled code.
Prepare time is when the RDBMS does syntax checking, and also validation of references. It must give an error if you name a table that doesn't exist, or invoke a function that doesn't exist.
You can't pass table names or function calls as parameters because then the RDBMS wouldn't be able to validate those references at prepare time. You shouldn't be able to use a query parameter to change the syntax of the statement, or introduce invalid tables or functions.
So the parameter placeholder must be an irreducible syntactic element that is never an invalid reference, i.e. a single literal value -- a number or a string.
No, because placeholders in prepared statements are automatically escaped. Thus in your case it will see, that it is a string and (at least) put it in quotes 'NOW()'. Now its just a string for mysql for mysql too. You can set the current datetime using PHP
$sthandler->bindValue(1, date('c'));
Related
This works as expected:
$st = $db->prepare('SELECT COUNT(1) WHERE EXISTS (SELECT * FROM ' . $table_name . ')');
$st->execute(array());
This throws a syntax error:
$st = $db->prepare('SELECT COUNT(1) WHERE EXISTS (SELECT * FROM :tblname )');
$st->execute(array('tblname' => $table_name));
As per the comments, "data can only be bound when it can be represented as a numeric or a quoted string literal. A table name can neither be a numeric or string literal."
What is the reasoning behind this?
Parameters are not just a string substitution. That's actually what makes them good at preventing SQL injection vulnerabilities.
(Well, in some clients, they emulate query parameters using string substitution. PDO has this behavior if you set the PDO::ATTR_EMULATE_PREPARES attribute. But I'll talk about true parameters.)
The reason that parameters are not string substitution is that they are combined with the SQL query after it has been parsed by the database server. The parsing step validates your SQL syntax is correct, and validates that the tables and columns you have named in the query in fact exist.
The only thing that can be omitted from the parsing step are constant values in expressions. The parser sees one of the parameter placeholders and knows that that token acts as if you had used a single scalar constant value. Therefore it can proceed with the syntax validation.
The following example is not valid SQL syntax because you can't SELECT from a constant string:
SELECT COUNT(1) WHERE EXISTS (SELECT * FROM 'mytable' )
But that's how the syntax will be interpreted if the table name is passed as a parameter. The SQL engine doesn't know the exact value of the parameter, but it knows it should be treated as a string value.
This way, the query can be parsed once (during the prepare() call), and then once parsed, you can execute the prepared query multiple times, plugging in different values for each execution (the execute() call). Those parameter values cannot change the logic of the query, only the constant values.
And that's the benefit for protecting against SQL injection. SQL injection occurs when dynamic content is combined with a query before it is parsed, so the content can change the syntax and therefore the logic of the query, making it do something you didn't intend. But if parameters are combined after parsing, they can't affect the logic.
In SQL, table, column, and schema names cannot be provided as parameters to prepared statements. It's not a PDO thing, it's a language restriction of SQL itself.
Many RDBMSs have semantics for preparing statements once and then using the prepared statements multiple times with different data. This is efficient because the RDBMS software works out the query plan – the operations it must do to satisfy the query – just once, and then reuses it with different data. If table or column names were variables in prepared statements the RDBMS software could not do that. PDO supports multiple RDBMSs so it enforces this language feature.
Prepared statements are also a security feature. When you provide parameters and use the statement, the data types of parameters get checked carefully. That would be much harder if table or column names could be parameters.
And, it's the way SQL was defined a generation ago. Data in RDBMSs lasts for decades.
As PDOStatement::execute documentation says, "All values are treated as PDO::PARAM_STR.
So I have following questions
1)Suppose I have a variable $_SESSION['id']=2 and a query
$sql='select * from articles where id=?';
$query=$con->prepare($sql);
$query->execute(array($_SESSION['id']));
When I execute the statements,it executes successfully.
It signifies that it passes variable as an integer .Does it do automatic casting or it violates the documentation statement?
2)Suppse I have another query
select * from articles where id=? and category=?
Now here first parameter is int and last parameter is string so when I bind parameters $query->bindValue(2, $_GET['category'],PDO::PARAM_STR); do I need to specify PDO::PARAM_STR or I can rely on the default implementation that it is considered as string by default as the documentation says.
3)I want to specify table name from $_GET['category'] into the query but I get the following format of the string(notice the quotes around table name) and hence SQL error.How can I correct it?
select * from 'article'...........
1) When I execute the statements,it executes successfully. It
signifies that it passes variable as an integer .Does it do automatic
casting or it violates the documentation statement?
No, it does pass the parameter as string. MySQL is casting it back to an int transparently because the column is an int value, which in this case has no side effects.
2) do I need to specify PDO::PARAM_STR or I can rely on the default
implementation that it is considered as string by default as the
documentation says.
Unless the API ever changes, it's pretty sure that it's bound as a string if the documentation says so. I doubt the API will change that any time soon, or at all. I might still explicitly bind it as string, just to make it extremely clear to a reader of the source code.
3) I want to specify table name from $_GET['category'] into the query
...
You cannot. You can only "placehold" values, not identifiers or other structural elements of the query. Parameterised statements are explicitly to separate between the structure of the query and the values dynamically interpolated into it; if you could dynamically interpolate structural elements there'd be no point to this separation.
I'm currently writing a php framework with focus on security. I use a query builder to generate SQL-statements, so that it is not bound to MySQL. (Or SQL in general) I found certain posibilities that user could inject row names, so it has to escape them somehow. Because of how the query builder works, i sadly cannot use prepared statements. How can I fix this?
EDIT:
The system works for example like this: db::select()-from('Tablename')->that('rowname')->run(). And I'm afraid one user could do something like that($_GET['foo']) or something. I could live with that, but I thought there has to be a way to sanatize this
To escape backtick you have to double it. Here is a function from my class
private function escapeIdent($value)
{
if ($value)
{
return "`".str_replace("`","``",$value)."`";
} else {
$this->error("Empty value for identifier (?n) placeholder");
}
}
//example:
$db->query("UPDATE users SET ?u=?s", $_POST['field'], $_POST['value']);
So, it will create a syntactically correct identifier.
But it is always better to whitelist it, as there can be a field, though with correct name,to which a user have no access rights. (So, schema-based solution is still dangerous from this point of view. Imagine there is a role field with value admin for the query from my example)
I have 2 functions in my class for this purpose, both accepts an array of allowed values.
Because of how the query builder works, i sadly cannot use prepared statements. How can I fix this?
If you can't use query parameters, then change the query builder to apply escaping to its arguments before interpolating them into SQL expressions.
Lots of people correctly advocate for query parameters, but escaping is also safe IF you do it correctly and consistently.
Cf. mysqli::real_escape_string()
Re your comment, okay I see where you're going. I was confused because you said "row name" and that's not the correct terminology. You must mean column name.
Yes, you're right, there are no functions in any of the MySQL APIs to escape table or column identifiers correctly. The escaping functions are for string literals and date literals only.
The best way to protect SQL queries when untrusted input names a table or column is to use allowlisting. That is, test the argument against a list of known table names or column names, which you either code manually, or else discover it from DESCRIBE table.
See examples of allowlisting at my past answers:
escaping column name with PDO
PHP PDO + Prepare Statement
My presentation SQL Injection Myths and Fallacies
My book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
I have a function that takes an array and creates a SQL statement based on they key/value pairs of the array. For example:
name=>SomeKittens
It'd turn into
(`name`) VALUES ('SomeKittens')
The only problem is when I use a MySQL string function such as NOW().
creation_date=>NOW()
turns into
(`creation_date`) VALUES ('NOW()')
Note that NOW() is escaped. Is there any way to detect if the value is a MySQL string function? (besides of course $value === "NOW()")
I'm using the Joomla DBO but am open to PDO/MySQLi solutions as well.
(relevant chat discussion)
If you allow functions with arguments I don't think you will be able to protect your db against SQL injections.
If you allow only functions w/o arguments (like NOW()) you might as well hardcode a list.
You may simply want to define a constant like MYSQL_NOW that when creating your query you know to convert to a NOW() function call rather than 'NOW()' string.
Historically, I've always used
mysql_real_escape_string()
for all input derived from users that ends up touching the database.
Now that I've completely converted over to MySQLi and I'm using prepared queries with bound parameters, have I effectively eliminated the possibility of SQL injection attacks?
Am I correct in saying I no longer need
mysql_real_escape_string()?
This is my understanding and the basis of a project of mine:
http://sourceforge.net/projects/mysqldoneright/files/Base/MysqlDoneRight-0.23.tar.gz/download
This is not something I want to get wrong though as now that I've released it, it could affect others as well.
All user provided input will now end up in bind_parms.
The queries provided in the prepare phase are static.
Yes. Using the prepared query will escape parameters.
It's not so simple. You can use bound parameters instead of interpolating application variables into SQL expressions in place of literal values only:
$sql = "SELECT * FROM MyTable WHERE id = ".$_GET["id"]; // not safe
$sql = "SELECT * FROM MyTable WHERE id = ?"; // safe
But what if you need to make part of the query dynamic besides a literal value?
$sql = "SELECT * FROM MyTable ORDER BY ".$_GET["sortcolumn"]; // not safe
$sql = "SELECT * FROM MyTable ORDER BY ?"; // doesn't work!
The parameter will always be interpreted as a value, not a column identifier. You can run a query with ORDER BY 'score', which is different from ORDER BY score, and using a parameter will be interpreted as the former -- a constant string 'score', not the value in the column named score.
So there are lots of cases where you have to use dynamic SQL and interpolate application variables into the query to get the results you want. In those cases, query parameters can't help you. You still have to be vigilant and code defensively to prevent SQL injection flaws.
No framework or data-access library can do this work for you. You can always construct a SQL query string that contains a SQL injection flaw, and you do this before the data-access library sees the SQL query. So how is it supposed to know what's intentional and what's a flaw?
Here are the methods to achieve secure SQL queries:
Filter input. Trace any variable data that gets inserted into your SQL queries. Use input filters to strip out illegal characters. For instance, if you expect an integer, make sure the input is constrained to be an integer.
Escape output. Output in this context can be the SQL query which you send to the database server. You know you can use SQL query parameters for values, but what about a column name? You need an escaping/quoting function for identifiers, just like the old mysql_real_escape_string() is for string values.
Code reviews. Get someone to be a second pair of eyes and go over your SQL code, to help you spot places where you neglected to use the above two techniques.
When you bind parameters to a prepared statement, it escapes the data automatically, so you shouldn't escape it before you send it through. Double escaping is usually a bad thing. At the very least, it produces ugly results with extra escaped characters later on.