Doctrine queryBuilder: SQL Injection risk in addOrderBy() method? - php

I a using Doctrine in a Symfony 2.8 project and I wonder if there is any risk of SQL Injections when using the addOrderBy() method of the queryBuilder:
// Order options. Real code does not specify this manually, but receives
// the options via user form input
$orderBy' = array(
'column1' => 'ASC',
'column2' => 'DESC',
...
'columnN' => 'ASC',
);
$qb = $this->em->createQueryBuilder();
...
foreach ($orderBy as $column => $orderOption) {
$qb->addOrderBy("e.$column", $orderOption);
// Does not work:
// $qb->addOrderBy("e.$column", ':orderOption')
// ->setParameter('orderOption', $orderOption);
//
// Error: Expected end of string, got ':orderOption'"
}
// Result is something like:
...ORDER BY e0_.column1 ASC, e0_.column2 DESC...
The problem is, that the order options are received via user form input that could be manipulated to something like ; DROP TABLE someTable instead of ASC or DESC.
I already tried this, but the query builder doesn't seem to accept multiple queries separated by ;, which does not mean, that there could not be any other/better injections :-)
Of course the problem could easily be solved by filtering the received results and skip all invalid search options. But I am trying to understand, if the addOrderBy() method in general. Is it save to pass any value to the method and Doctrine will handle the rest, or is there a potential risk?
I wonder why the ->setParameter() method does not work, as it would when using ->where().

Short answer is that column names submitted by form could in fact be used for a sql injection attack. Doctrine assumes you have properly validated column (and table) names.
The doctrine code is fairly easy to read and it's worth taking a look at for these sorts of questions:
public function addOrderBy($sort, $order = null)
{
$orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
return $this->add('orderBy', $orderBy, true);
}
Note that there is no value at all in using Expr in your queries. Doctrine takes care of generating them for you.
$this->add is a bit more complicated but basically the second argument ends up being passed along with no escaping or filtering etc.
I wonder why the ->setParameter() method does not work, as it would
when using ->where()
The important concept is that prepared statements only protect values not column or table names.
So again, it entirely up to you to filter table/column names coming from the wild. And looking at the source can be informative.

You can use the Expr class: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html#the-expr-class
Or a simple function/method to return a valid value:
function orderOption($option, $defaultOption = 'ASC') {
if (in_array(strtoupper($option), ['ASC', 'DESC']) {
return $option;
}
return $defaultOption;
}

Related

How to safely use possibly unsafe data with Query::select()?

First all, sorry about my english!
I'm trying do a dynamic select in CakePHP when i need change the statements of search. I know can use a variable to do that, but i think about SQL injection. It has other way to do this ?
Example:
$var = "quantidade";//$var can be other values.
$query->find('')->select('quantidade' => $var);
Unlike in other places like Query::where() when using the key => value syntax, there are no SQL injection prevention mechanisms in Query::select(), values passed to this method are being inserted into the query as is (they might get quoted, but not escaped), so you have to take care of securing this yourself.
I'd suggest to use a whitelist, either a hardcoded one:
$allowedFields = [
'field_a',
'field_b'
];
if (in_array($var, $allowedFields, true)) {
$query = $Table
->find()
->select([
'alias' => $var
]);
}
or one retrieved from the tables schema in case you want to allow all fields, but be careful, only do this if it's definitely safe to expose all possible columns to the user!
$allowedFields = $Table->getSchema()->columns(); // use schema() in CakePHP < 3.4
See also
Cookbook > Database Access & ORM > Query Builder > SQL Injection Prevention
Cookbook > Database Access & ORM > Schema System
API > \Cake\ORM\Table::getSchema()
API > \Cake\ORM\Table::schema()

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.

Pass sanitized input as column name in where clause

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.

Doctrine: How can I have a where clause included only when necessary in a query?

I want to use a single query to retrieve:
items of any categories (no filter applied);
only items of a single category (limited to a particular category);
For that purpose I should be able to write a Doctrine query which would include a where clause only when certain condition is met (eg. part of URL existing), otherwise, where clause is not included in the query.
Of course, i tried with using the If statement, but since doctrine query is chained, the error is thrown.
So i guess the solution might be some (to me unknown) way of writing doctrine queries in an unchained form (by not having each row started with "->" and also having each row of a query ending with semicolon ";")
That way the usage of IF statement would be possible i guess.
Or, maybe there's already some extremely simple solution to this matter?
Thanks for your reply!
I am unfamiliar with Codeigniter but can't you write something like this?
$q = Doctrine_Query::create()
->from('items');
if ($cat)
$q->where('category = ?', $cat);
In your model pass the condition for where as a parameter in a function.
In below example i am assuming the function name to be filter_query() and passing where condition as a parameter.
function filter_query($condition=''){
$this->db->select('*');
$this->db->from('TABLE NAME');
if($condition != ''){
$this->db->where('condition',$condition);
}
}
In above example i have used Codeigniter's Active Record Class.

PHP/Mysql - Dynamically selecting data?

I'm a bit new to OOP, but i've been playing with it for about a month now. Usually, i create a class called Mysql which has a __construct function that connects to a database directly. And after that i have lots of different functions that gets or inserts data into different tables.
On the bus home today, i began thinking and i came up with a brilliant idea that would make it less cluttered. My idea is to use one single function that selects data (and one for inserting), and depending on how the query that's passed in looks, it will select different data from different tables. Nice, right?
But i'm kind of stuck here. I'm not sure at all how to achieve this. I've got a small clue how it could work, but i don't know how i would bind the results, or fetch them into an array. The query will be created in another method, and then be passed into the select/insert function within the Mysql class.
I drew a "sketch" on how i think it may work. Here it is:
Of course, the function below will be placed in the Mysql class, and will already have connection to a database.
// This is an example query that could be passed in.
$query = "SELECT * FROM table WHERE id=150";
function select_data($query) {
if ( $smtp = $this->conn->prepare($query) ) {
$smtp->execute();
$smtp->bind_results(What happens here?);
if ( $smtp->fetch() ) {
foreach ( fetched_row? as $key => $value ) {
$return[] = $key => $value;
}
return $return;
}
else return NULL;
}
else return $this->conn->error;
}
Thanks a lot to anyone who can show me how this can be achieved.
You have more options to use in PHP and they has their own specifics.
I can recommend some ORM
like Doctrine because of ease of use, stability, community and most importantly efectivity.
You can use it as easy as:
$q = Doctrine_Query::create()
->select('u.username, p.phone')
->from('User u')
->leftJoin('u.Phonenumbers p');
$users = $q->fetchArray();
or:
// Delete phonenumbers for user id = 5
$deleted = Doctrine_Query::create()
->delete()
->from('Phonenumber')
->andWhere('user_id = 5')
->execute();
// Make all usernames lowercase
Doctrine_Query::create()
->update('User u')
->set('u.username', 'LOWER(u.username)')
->execute();
// 'like' condition
$q = Doctrine_Query::create()
->from('User u')
->where('u.username LIKE ?', '%jwage%');
$users = $q->fetchArray();
I think you are running into problems when you need related data. In other words, when an object of yours has a property which is another object that data should also be gathered and dynamically filled. I once came pretty far but when stuff like INNER, LEFT and RIGHT joins come accross you'll think twice about going further ;)
About bind_results:http://php.net/manual/en/mysqli-stmt.bind-result.php
(Maybe slightly off topic; SMTP? That's a mailprotocol, are you sure you don't mean MySQLi's STMT?)
For reference, PDO already does a lot of what you seem to want to do. It even has a fetchAll method that returns an array of rows, much like your function does. You don't need to bind anything in order to use it, unless you have parameters in your query string (and of course, values to bind to those parameters).
Check out the PDO documentation, and see if that doesn't fit your needs. Particularly PDOStatement->fetchAll().

Categories