• $table is used to directly generate an alias, the base table name is escaped but the alias is not
  • column names are not escaped at all
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Damien Tournoud’s picture

I don't believe this is exploitable in core. We cannot guess what usage contrib will do of the API. We need to make sure that, as long as you use the query builder (and do not craft SQL queries by hand) you are completely protected against injections.

Gábor Hojtsy’s picture

Agreed. Mixed escaping of items is not a good idea as certain form API elements showed. I'd vote to consistently escape items passed to the query builder.

Crell’s picture

Berdir’s picture

EDIT: Screw that, we obviously want to use http://api.drupal.org/api/function/db_escape_table/7. But the point below is still true.

Also, allowing not validated user input as table/columns into a query sounds like a recipe for disaster to me anyway. For example, even if the table name is escaped, it would still allow access to every table in your system and maybe reading of data like passwords and so on. So we need to document/clarify that it is *never* safe to use user submitted values directly for building a query. You always need to use a whitelist approach.

catch’s picture

Yeah I'm not really sure how it's possible to protect people entirely from this, seems more a documentation issue, for the points Berdir raised. Although db_escape_table() obviously makes sense.

Crell’s picture

Under no circumstances do we call db_escape_table() from within the query builder. Calling the appropriate connection object's escapeTable() method, however, is a good idea.

We should also make a note of this fact in the appropriate handbook pages at least.

andypost’s picture

table_sort is first candidate for injections

Berdir’s picture

Status: Active » Needs review
FileSize
2.4 KB

Ok, attached is a first patch. I escaped the table alias and the column name in all places I could find. I tried to do the escaping as late as possible (read: in __toString()/compile()).

Let's see what the test bot thinks about this. Not sure about the docs part yet...

bshort’s picture

Subscribe

Status: Needs review » Needs work

The last submitted patch, escape_identifiers.patch, failed testing.

jpmckinney’s picture

In openid.module:

  db_delete('openid_association')
    ->condition('created + expires_in', REQUEST_TIME, '<')
    ->execute();

escapeTable() will turn "created + expires_in" into "createdexpires_in".

In database_test.test:

    $query->havingCondition('COUNT(task)', 2, '>=');

escapeTable() will turn "COUNT(task)" into "COUNTtask".

In short, don't use escapeTable() to escape fields.

jpmckinney’s picture

Status: Needs work » Needs review
FileSize
1.61 KB

Re-rolled without offending lines.

Berdir’s picture

Status: Needs review » Needs work

Yep, and the other one is a havingCondition() in the tests. So we need to convert the above into where() and the test case to having().

Or we could re-think if we really want this... As stated above, it doesn't make things secure but it might confuse developers unless it's very well documented.

EDIT: #12 only escapes the alias, which is basically the Or...

Berdir’s picture

Status: Needs work » Needs review
jpmckinney’s picture

FileSize
3.18 KB

EDIT: #12 only escapes the alias, which is basically the Or...

I don't understand. What do you mean?

Anyway, I re-rolled it with updated documentation (havingCondition didn't even have a PHPDoc) and substitution of where() and having() for condition() and havingCondition(). I am +1 for doing this.

Not immediately relevant to this issue, but related:

QueryConditionInterface::condition was lying in saying that the default operator is =; it's actually IN or =. I don't think havingCondition should have a default operator of =. If $values is an array, it should use IN. I made these changes as well.

Also, I have no idea why havingCondition and SelectQuery::condition count the number of args and pass it around, seeing as, as far as I can tell, that argument is never used. So, I cut it out. I'm prepared to be corrected, or asked to create a new issue!

jpmckinney’s picture

FileSize
4.03 KB

Wrong patch in #15. This is correct.

jpmckinney’s picture

FileSize
5.35 KB

Forgot to include patch #8 in my patch. I should not write patches and listen to sessions at the same time.

Status: Needs review » Needs work

The last submitted patch, 769554-17.patch, failed testing.

jpmckinney’s picture

Status: Needs work » Needs review
FileSize
5.35 KB

Fix syntax.

Status: Needs review » Needs work

The last submitted patch, 769554-17.patch, failed testing.

jpmckinney’s picture

FileSize
5.35 KB

One more time.

jpmckinney’s picture

Status: Needs work » Needs review
Berdir’s picture

+++ includes/database/select.inc
@@ -942,11 +939,29 @@ class SelectQuery extends Query implements SelectQueryInterface {
   /* Implementations of QueryConditionInterface for the HAVING clause. */
 
-  public function havingCondition($field, $value = NULL, $operator = '=') {
-    if (!isset($num_args)) {
-      $num_args = func_num_args();
-    }
-    $this->having->condition($field, $value, $operator, $num_args);
+  /**
+   * Helper function to build most common HAVING conditional clauses.
+   *
+   * This method can take a variable number of parameters. If called with two
+   * parameters, they are taken as $field and $value with $operator having a value
+   * of IN if $value is an array and = otherwise.
+   *

I think we currently only document interfaces, not the implementations (I'm not 100% sure how this will change with #718596: Lacking standards for documenting classes, interfaces, methods). So if havingCondition() isn't documented in SelectQueryInterface, we should add it there.

What I meant in #13 was, that your patch in #12 means not to escape column names and #21 means to escape column names. Both are possible approaches as we can't have a 100% secure implementation anyway. So we need to decide which one we want to proceed. Any comments on this?

114 critical left. Go review some!

jpmckinney’s picture

FileSize
5.63 KB

I think we should escape column names in condition() and havingCondition(). If a developer wants to circumvent that, there are where() and having(). Here is an updated patch with the PHPDoc in the right place.

Status: Needs review » Needs work

The last submitted patch, 769554-24.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review

#24: 769554-24.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 769554-24.patch, failed testing.

jpmckinney’s picture

The only change from the previous patch was adding havingCondition to QueryConditionInterface. Could this cause installation to fail? I'm not as familiar with PHP interfaces.

Berdir’s picture

It should be added to SelectQueryInterface. QueryConditionInterface is then used internally in SelectQuery.

jpmckinney’s picture

Status: Needs work » Needs review
FileSize
6.16 KB

Ok, did that now.

moshe weitzman’s picture

i'm fine with this as long we don't regress on #715132: Over aggressive escaping of table names

jpmckinney’s picture

#715132: Over aggressive escaping of table names should not be affected, but we can add tests for that, I think.

Status: Needs review » Needs work

The last submitted patch, 769554-30.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
6 KB

Removed the .htaccess part that you accidently added, no other changes.

Crell’s picture

Status: Needs review » Needs work

Well #34 fixes a half dozen brain dead bugs I'm embarrassed were even still in there (lack of havingCondition docs, $num_args, etc.). I agree with #11, though, that we should not be using escapeTable() on a field. There's on guarantee that the escaping rules will always be the same and on every database. Let's introduce an escapeField() method and use that instead, even if its body is the same as escapeTable()'s. That should be a cleaner, more forward-looking approach.

jpmckinney’s picture

Status: Needs work » Needs review
FileSize
6.51 KB

As far as I can tell, the last patch didn't escape any fields. Maybe we want to start doing that? Anyway, here's a patch that adds escapeField().

Crell’s picture

Status: Needs review » Needs work

Hm. OK, I misread the previous patch then. Yeah, we should be escaping field names, for the same reason we should be escaping table names. Let's add that. Also, I'm pretty sure field names can't (or at least really shouldn't) contain periods, so we can remove that from the allowed character list for fields.

cha0s’s picture

Status: Needs work » Needs review
FileSize
6.51 KB

Rerolled per #37

jpmckinney’s picture

I think #38 just removes the period from escapeField.

cha0s’s picture

Status: Needs review » Needs work

Oh, I must have misread the patch... I saw the escapeField() function and assumed it had already been implemented. My bad.

jpmckinney’s picture

Hmm? Removing the period from the allowed character list was part of the request in #37, so that's good.

But, we also want to start using escapeField to escape fields. That's all that's missing from the patch now.

Berdir’s picture

Status: Needs work » Needs review
FileSize
8.37 KB

Escaped fields in conditions and SelectQuery, also added a db_escape_field() (similiar to db_escape_table).

Status: Needs review » Needs work

The last submitted patch, escape_fields.patch, failed testing.

Berdir’s picture

FileSize
8.37 KB

Also, I'm pretty sure field names can't (or at least really shouldn't) contain periods, so we can remove that from the allowed character list for fields.

Field names not, but we obviously also want to allow to specify the table in a condition and for that, you need the . :)

$query->condition('table.field');

This caused all kind of funny exceptions because it removed the ....

Berdir’s picture

Status: Needs work » Needs review
Crell’s picture

Status: Needs review » Reviewed & tested by the community

I like it. Fixes a couple of issues all at once. Rock on!

*Turns to Dries, who is sitting next to him at the CommerceGuys table at a conference.* All yours.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Thanks! Committed to CVS HEAD. Now: lunch time!

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.