Pass sanitized input as column name in where clause - php

I have a function that accepts a $filter argument and then pulls data from an SQL table based on the filters in the argument. At first I tried overloading the function so that one function took a single $filter variable and another took an array for multiple filters. But then, I started wondering how I could sanitize the filter tag.
That may have been confusing so here are some examples. For example, a user types in the search box to display all users with the name John. So, $filter_tag would be set to say 'name' and $filter would be set to say 'John'. My PDO query would look something like this:
$query = "SELECT `name` FROM `users` WHERE ";
$query .= $filter_tag." = ?";
The issue is that $filter_tag is not sanitized. If I do sanitize it and the variable is escaped, then the query will not work. Maybe I am making this more complicated than it needs to be and there is some simple solution.
Please comment if you do not understand something that I am asking.

You could create a whitelist of valid tags:
if (in_array($filter_tag, ['name', ...], true)) {
$query .= $filter_tag . = '?';
}
Alternately you could remove all invalid characters, but I prefer the whitelist approach, because there are only that many valid column names :)
Lastly, instead of the above code you could also turn the condition around and raise an error if the given tag doesn't appear in the whitelist. In some cases this may be the better approach, because otherwise you may get an error later on because the number of arguments passed to ->execute() should match the number of placeholders in the query.

Related

How to assign the correct type to bind_param dynamically based on retrieved column types?

Background info:
I am creating a class which will handle certain queries for me. One of these queries would be a SELECT query, where the user can select something based on a clause. I successfully retrieved the TYPES of the columns, but have no idea on how to actually decide based on the TYPE of the column which TYPE should be given to bind_param() (the first parameter).
My original plan was to cheap out and simply hard-code which argument should be given if the retrieved column type is a certain type.
Example of my original idea:
if ($type === "varchar") {
$aVariable = "s";
}
//OR USE A SWITCH, BUT YOU GET THE IDEA
bind_param($aVariable, $someOtherVar);
However this isn't something I am satisfied with because of numerous different reasons. So my question is:
Question:
How can I use the column TYPES (if it's possible) to determine whether the argument given to bind_param() should be one of the following types: "s, i, d, b", without having to hard-code that the retrieved type = a certain letter.
Guessing the parameter type always smells. Whereas setting all parameters to "s" works flawless most of time.
So let me suggest you another solution. Make the types explicit but optional. By default it will be "s" but with a possibility to define the types manually, just like I did in my mysqli helper function:
function prepared_query($mysqli, $sql, $params, $types = "")
{
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
}
when you don't need any sprecific types (most of time), just leave them out:
$sql = "SELECT * FROM tmp_mysqli_helper_test WHERE id > ?";
$res = prepared_query($conn, $sql, [1])->get_result();
but every time you need it, it's already here and explicit, so you could set the exact type you want:
$sql = "SELECT * FROM tmp_mysqli_helper_test WHERE id > ?";
$res = prepared_query($conn, $sql, [1], "i")->get_result();
simple, clean and concise
Your code should never try to guess the type. You should bind everything as string. 99.99% of the time it will make no difference. There are some edge cases when the actual type make a difference, but in these situations you can hardcode the type when you know what the type should be. If you guess the type then you will only add more bugs into your software.
All the data you receive from HTML form is of type string. MySQL does the type casting dynamically depending on the context. Why should you bother with casting the value in PHP? Simply send everything as is to MySQL and let it decide what should the type be.

How to use wildcard in PHP query

I have a table filter feature in PHP club membership webpage. I made it so the user can filter the table and choose which members to display in a table. For example, he can choose the country or state where the member is from then hit display. I am using a prepared statement.
The problem is, I need to use wildcards to make the coding easier. How do I use a wildcard in PHP MySQL query? I will use wildcards for example if the user does NOT want specific country but instead he wants to display all members from all countries.
I know not specifying the WHERE country= will automatically select any countries but I already constructed it so each controls like the SELECT control for country already has a value like "CA" or "NY" and "*" if the user leaves that control under "All Countries". This value when submitted is then added to the query like:
$SelectedCountry = $_POST["country"];
sql .= " WHERE country=" . $SelectedCountry;
But the problem is using WHERE country=* doesn't seem to work. No errors, just doesn't work. Is "*" the wildcard in PHP MySQL?
The * is not a wildcard in SQL when comparing with the = operator. You can use the like operator and pass a % to allow for anything.
When doing this the % should be the only thing going to the bind. $Bind_country = "'%'"; is incorrect because the driver is already going to quote the value and escape the quotes. So your query would come out as:
WHERE country ='\'%\''
The = also needs to be a like. So you want
$bind_country = '%';
and then the query should be:
$sql = 'select * from table where country like ?';
If this were my application I would build the where part dynamically.
Using * in WHERE clause is not right. You can only give legit value. For example:
// looking for an exact value
SELECT * FROM table WHERE column = 'value'
// you can also do this when looking for an exact value
// it works even if your $_POST[] has no value
SELECT * FROM table WHERE column = 'value' OR '$_POST["country"]' = ''
// looking for a specific or not exact value
// you can place % anywhere in value's place
// % denotes the unknown characters of the value
// it works also even if your $_POST[] has no value
// results will not be the same when you're using AND or OR clause
SELECT * FROM table WHERE column LIKE '%val%'
I think below link can solve your problem.
Just have a look and choose what you need.
Thanks.
http://www.w3schools.com/sql/sql_wildcards.asp

how to get the where clause in string format using CakePHP3 ORM?

In CakePHP3, there is a ORM that helps with building queries.
From the documentation, I can see that
$query = $articles->find(); // build a query that has not run yet
$query->where(['id' => 1]); // Return the same query object
So in this case, I want the string
WHERE `articles`.`id` = 1
After much googling, I found out that there is a way to return just the where clause of a query object.
$query->where(['id' => 1])->clause('where'); // Return the where clause in the form of a QueryExpression
More googling leads me to find out how to get the QueryExpression to spit out string representation
$query->where(['id' => 1])->clause('where')->sql($valueBinder); // Return the where clause in string format
Here is my problem. I don't know what the $valueBinder is supposed to look like. I don't know how to initialize it.
I am also happy not to use ValueBinder as long as I can get the where clause in string format using CakePHP 3 ORM and in the right SQL dialect. Please assume I am using MySQL.
Please advise.
EDIT
I tried to use $query->valueBinder() as the $valueBinder.
It is empty and does not contain the associated c:0 to the value 1.
To directly answer your question, you can get the SQL for any clause this way:
$binder = new \Cake\ORM\ValueBinder();
$query->clause('where')->sql($binder);
That will return the SQL with the correct placeholders, not with the values to be used. The values live in the $binder variable and are used for statement objects.
As I can see, you only wanted to preserve the internal structure of the where clause to pass it to another query in a different request. Your solution is fine, but I'd like to add that you can also encode a full conditions tree from an existing query:
$where = serialize($query->clause('where'));
$anotherQuery->where(unserialize($where)); // A query in another request
In any case, you need to be careful with what you are unserializing as taking it directly from user input will certainly lead to security problems.
You can choose to omit this param if you like. Please see http://api.cakephp.org/3.0/class-Cake.Database.Query.html#_sql
In addition, you can use the Query member function traverse($visitor, $parts) to isolate the where clause. $visitor is a function that takes a value and a clause. You define the behavior of $visitor. $parts is an array of clause names. I suggest passing array('where') into this param.
My workaround is that I store the conditions in json string format.
Using the same example, what I do is
$data['conditions'] = json_encode(['Articles.id' => 1]); // encode into JSON string
$this->DynamicRules->patchEntity($dynamicRule, $data); // use in edit action of DynamicRulesController
then when I need to reuse the conditions, I do:
$articlesTable = TableRegistry::get('Articles');
$query = $articlesTable->find(); // new query for Articles
$rule = json_decode($dynamicRule->conditions, true); // get back the conditions in associative array format
$query->where($rule); // re-assign the conditions back
This got me what I ultimately wanted.

Mysql - PHP different values searches

I know how to perform mysql searches using for example the WHERE word. But my problem is that i need to search on different values, but these can vary in number. For example:
I can search for 3 variables Name, LastName, Age
BUT
I in other search, i can look for 2 variables Name, Age.
Is there a way to perform a MYSQL search with the same script, no matter the quantity of values i search.??
Ot it is a better practice to "force" the search of a fixed amount of variables.??
Thanks.!
Roberto
IMHO, it is far better to limit the search to a fixed number of variables. That way you are answering a specific question for a specific reason, not trying to fit a general answer to your specific question. Limiting the search criteria makes the statement(s) easier to debug and benchmark for performance.
Hope this helps.
Just use a variable for your search parameters and inject that into your query. Just ensure that in the function/method you put the variable into the proper format (which will depend on how you select the different values.)
SELECT *
FROM db
$variable;
There will be no WHERE clause seen, unless it is passed your values (meaning you can use this same query for a general search of the db) without fear of having an empty/required $variable.
Your $variable when constructed would need to have to have the WHERE clause in it, then each value you add, insert it (in a loop perhaps) in the proper format.
Hope this makes sense, if not let me know and I will try to clarify. This is the same method most people use when paginating (except they put the variable in the LIMIT instead of the WHERE)
EDIT:
Also make sure to properly sanitize your variable before injection.
Simple example of dynamically building a query:
$conditions = array();
if (...) {
$conditions['age'] = $age;
}
if (...) {
$conditions['name'] = $name;
}
...
if (!$conditions) {
die('No conditions supplied');
}
// if you're still using the mysql_ functions and haven't done so before:
$conditions = array_map('mysql_real_escape_string', $conditions);
foreach ($conditions as $field => &$value) {
$value = "`$field` = '$value'";
}
$query = 'SELECT ... WHERE ' . join(' AND ', $conditions);
It's really not hard to dynamically cobble together the exact query you want to create. Just be careful you don't mess up the SQL syntax or open yourself to more injection vulnerabilities. You may want to look at database abstraction layers, which pretty much allow you to pass a $conditions array into a function which will construct the actual query from it, more or less the way it's done above.

"SELECT * FROM users WHERE id IN ( )" == FAIL

I have a function that I use called sqlf(), it emulates prepared statements. For instance I can do things like:
$sql = sqlf("SELECT * FROM Users WHERE name= :1 AND email= :2",'Big "John"','bj#example.com') ;
For various reasons, I cannot use prepared statements, but I would like to emulate them. The problem that I run into is with queries like
$sql = sqlf("SELECT * FROM Users WHERE id IN (:1)",array(1,2,3) );
My code works, but it fails with empty arrays, e.g. the following throws a mysql error:
SELECT * FROM Users WHERE id IN ();
Does anyone have any suggestions? How should I translate and empty array into sql that can be injected into an IN clause? Substituting NULL will not work.
Null is the only value that you can guarantee is not in the set. How come it is not an option? Anything else can be seen as part of the potential set, they are all values.
I would say that passing an empty array as argument for an IN() clause is an error. You have control over the syntax of the query when calling this function, so you should also be responsible for the inputs. I suggest checking for emptiness of the argument before calling the function.
Is there a possibility that you could detect empty arrays withing sqlf and change the SQL to not have the IN clause?
Alteratively, you could postprocess the SQL before passing it to the "real" SQL executor so that "IN ()" sections are removed although you'd have to do all sorts of trickery to see what other elements had to be removed so that:
SELECT * FROM Users WHERE id IN ();
SELECT * FROM Users WHERE a = 7 AND id IN ();
SELECT * FROM Users WHERE id IN () OR a = 9;
would become:
SELECT * FROM Users;
SELECT * FROM Users WHERE a = 7;
SELECT * FROM Users WHERE a = 9;
That could get tricky depending on the complexity of your SQL - you'd basically need a full SQL language interpreter.
If your prepare-like function simply replaces :1 with the equivalent argument, you might try having your query contain something like (':1'), so that if :1 is empty, it resolves to (''), which will not cause a parse error (however it may cause undesirable behavior, if that field can have blank values -- although if it's an int, this isn't a problem). It's not a very clean solution, however, and you're better off detecting whether the array is empty and simply using an alternate version of the query that lacks the "IN (:1)" component. (If that's the only logic in the WHERE clause, then presumably you don't want to select everything, so you would simply not execute the query.)
I would use zero, assuming your "id" column is a pseudokey that is assigned numbers automatically.
As far as I know, automatic key generators in most brands of database begin at 1. This is a convention, not a requirement (auto-numbered fields are not defined in standard SQL). But this convention is common enough that you can probably rely on it.
Since zero probably never appears in your "id" column, you can use this value in the IN() predicate when your input array is empty, and it'll never match.
The only way I can think to do it would be to make your sqlf() function scan to see if a particular substitution comes soon after an "IN (" and then if the passed variable is an empty array, put in something which you know for certain won't be in that column: "m,znmzcb~~1", for example. It's a hack, for sure but it would work.
If you wanted to take it even further, could you change your function so that there are different types of substitutions? It looks like your function scans for a colon followed by a number. Why not add another type, like an # followed by a number, which will be smart to empty arrays (this saves you from having to scan and guess if the variable is supposed to be an array).

Categories