16

I am making a Doctrine query and I have to do a wildcard match in the where clause. How should I escape the variable that I want to insert?

The query I want to get:

SELECT u.* FROM User as u WHERE name LIKE %var%

The php code until now:

   $query = Doctrine_Query::create()
                ->from('User u')
                ->where();

What should come in the where clause? The variable I want to match is $name

Peter Smit
  • 27,696
  • 33
  • 111
  • 170

2 Answers2

39

Nobody answered your question correctly, so I'll make a stab at it.

->where('u.name LIKE ?', array("%$name%"));
->where('u.username LIKE ?', '%'.$username.'%')

Neither of these are safe. Let me explain a few scenarios.

Scenario 1

Imagine you want to let users search for matching usernames, but you never want to list ALL usernames. Perhaps you don't want somebody to easily steal a list of a million usernames from you. somewhere prior to this code, you did something like this:

if (strlen(trim($name)) < 5) throw Boogey_Monster_Exception();

You thought this would prevent somebody from leaving the field blank and pulling down a list of all usernames... but in reality the user can submit "_____" or "%%%%%" or anything similar to get a list of all usernames, not just matching 5 or more known characters.

I have personally seen this form of attack used on several large, public websites.

Scenario 2

You have a website with lots of users and lots of user data. You have 10,000,000 rows in your user table. You want to enable site's users to find another user's username by searching for known prefixes.

So you write some code like this, modified slightly from the example above to only have a wildcard AFTER the search string.

->where('u.name LIKE ?', array("$name%"));

If you have an index on u.name, then this LIKE query will use the index. So if the user submits $name="john", then this query will efficiently match users like johndoe, johnwayne, johnwaynegacy, etc.

However, if the user submits $name="%john" instead, this query no longer uses the index and now requires a full table scan. On a very large database this can be a very slow query.

The MySQL manual on SQLi mentions this same thing (pages 78-79) and I googled for some examples of slow query performance and found one link.

This may not sound like a big deal, but for sites backed by an RDBMS, the RDBMS is usually a significant bottleneck, and much of the performance engineering revolves around reducing contention on the RDBMS. IF you have a handful of users launching an attack that ties up a database handle for 60+ seconds, and you have a small pool of database handles, you can see how this could quickly scale to monopolize all of your database handles and prevent legitimate users from being able to get one.

Links

http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf

http://forums.mysql.com/read.php?24,13397,13397

Solution

Anyway, the better solution (as mentioned in the MySQL manual linked above and by commenter @Maxence, is to use addcslashes()):

$username = addcslashes("%something_", "%_");

Note that since the sql examples here use prepared statements, which are completely immune to sql injection, it is not necessary or desirable to use mysql_real_escape_string(); the escaping it performs is solely to prevent sql injection. What we're trying to prevent is wildcard injection, and that requires a function that escapes the two sql wildcard characters, '%' and '_'.

Vebjorn Ljosa
  • 17,438
  • 13
  • 70
  • 88
Mark E. Haase
  • 25,965
  • 11
  • 66
  • 72
  • 2
    I think that a good solution to avoid high-burden on the DB is to avoid LIKE queries and to use a search engine like [sphinxs](http://sphinxsearch.com/). It is fantastic! – JeanValjean Jun 02 '12 at 20:44
  • 1
    @mehaase I saw you gave -1 to other answers, but your method is unsafe too because you forgot to escape the escape character itself. So, at least it should look like this: `addcslashes('%something_', '\\%_');`. Keep in mind that in MySQL the `'\\something' LIKE '\\something'` evaluates to `FALSE` but `'\\something' LIKE '\\\\something'` evaluates to `TRUE` ;-) – Karolis Jan 21 '13 at 23:39
  • err... the question does not specify either of your two scenarios. The info in this answer is good to think about but your `-1` to the other answers seems wrong to me... the other answers are correct for the question. – Jordan Lev Feb 07 '14 at 05:36
  • 1
    @JordanLev you missed the point. The question is how to escape this query. If you don't escape wild cards, then you haven't correctly escaped the query. My answer shows why wild cards are potentially dangerous as well as how to escape them. I -1 the other questions for being mistaken. It's a common mistake, but it's still a mistake. If you have any specific feedback, please let me know. I always like to correct misinformation. – Mark E. Haase Feb 08 '14 at 15:20
  • @mehaase , I understand your point, and I do appreciate the info you've provided here. One specific detail to add is that your solution does not apply to SQLite databases, because c-style escaping is not utilized unless you explicitly tell it to use backslash as an escape character for a particular `LIKE ...` condition. (See the "Literal Values" section of https://www.sqlite.org/lang_expr.html ). Unfortunately I don't know how to make this work with Doctrine's query builder (I am only using the DBAL), but the concept is explained here: http://stackoverflow.com/a/7323498/477513 . – Jordan Lev Feb 08 '14 at 22:20
  • @JordanLev Doesn't $queryBuilder->setParam('val', '%' . addcslashes($value) . '%') work for doctrine? – zozo Dec 04 '19 at 10:54
-3

Something bad happened to Doctrine's documentation so here's the Google copy (check Like Expressions section)

...
->where('u.name LIKE ?', array("%$name%"));
Crozin
  • 43,890
  • 13
  • 88
  • 135
  • 3
    What happens if there is a special character like '%' or '\_' in $name ? You should add addcslashes($name, '%\_') – Maxence Jun 13 '11 at 12:53
  • `addcslashes();`? There's no need to add slashes to anything for queries, whether you're using and ORM or native sql. Doctrine takes care of escaping properly without littering your data with slashes. For native mysql, use `mysql_real_escape_string($string);`. For others, check your docs; just don't do `addcslashes();` – adlawson Jul 14 '11 at 10:13
  • 7
    @adlawson: Doctrine **won't** escapse `%` and `_` for `LIKE` expression. This has to be handled manualy. – Crozin Jul 14 '11 at 10:27
  • 2
    -1 Not safe. Although in fairness, few developers understand the difference between = and LIKE. (They think LIKE is a superset of = that adds magical pattern matching powers. It's not.) Not escaping wildcards in a LIKE could lead to data disclosure or denial of service (by running an exceptionally complicated pattern query). – Mark E. Haase Oct 25 '11 at 17:11
  • If there is a percentage sign in the the name, then the wildcard matching matches some extra things. This *might* be bad, but it also might be irrelevant (depending on the situation)... so just saying that this answer is flat-out wrong is not accurate. – Jordan Lev Feb 07 '14 at 05:38
  • @JordanLev you need to consider that (a) the OP has not provided enough details to say whether unescaped wild cards are desirable or not, and (b) over 3,000 people have viewed this question. Presumably those 3,000 did not all have the same exact question as the OP but we're looking for something similar. Therefore, a good SO answer should address the most general case allowed by the question. – Mark E. Haase Feb 08 '14 at 15:24