24

Basically, what I want to do is this:

mysql_query("SELECT ... FROM ... ORDER BY $_GET[order]")

They can obviously easily create a SQL error by putting non-sense in there, but mysql_query only allows you to execute 1 query, so they can't put something like 1; DROP TABLE ....

Is there any damage a malicious user could do, other than creating a syntax error?

If so, how can I sanitize the query?

There's a lot of logic built on the $_GET['order'] variable being in SQL-like syntax, so I really don't want to change the format.


To clarify, $_GET['order'] won't just be a single field/column. It might be something like last_name DESC, first_name ASC.

Paul Sonier
  • 38,903
  • 3
  • 77
  • 117
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 4
    Does your language/DB binding offer any way to do bind parameters? This way you are bound to run into problems sooner or later, regardless of the specifics of the query. – drxzcl Jul 11 '11 at 18:03
  • 5
    Just because they may not be a way now is no reason to give them the option in the future. – Drazisil Jul 11 '11 at 18:08
  • 1
    http://2600nl.net/2010/05/29/exploiting-sql-injection-in-order-by-clause-mysql-5/ – Kevin Stricker Jul 11 '11 at 18:08
  • 2
    Just because there were too many incorrect answers mentioning to use `mysql_real_escape_string()`: Even using that function there is still a vulnerability. See the link in @Rafe's answer. – Lotus Notes Jul 11 '11 at 18:18
  • Ok, deleted my post about UNION injection, if someone wonders, UNION injections with order by are quite had because using UNION after an order by can only be done if parentheses are used around the queries. – regilero Jul 14 '11 at 08:57
  • @regilero: Which there aren't any :) – mpen Jul 14 '11 at 20:30

5 Answers5

32

Yes, SQL injection attacks can use an unescaped ORDER BY clause as a vector. There's an explanation of how this can be exploited and how to avoid this problem here:

http://josephkeeler.com/2009/05/php-security-sql-injection-in-order-by/

That blog post recommends using a white list to validate the ORDER BY parameter against, which is almost certainly the safest approach.


To respond to the update, even if the clause is complex, you can still write a routine that validates it against a whitelist, for example:

function validate_order_by($order_by_parameter) {
    $columns = array('first_name', 'last_name', 'zip', 'created_at');

    $parts = preg_split("/[\s,]+/", $order_by_parameter);

    foreach ($parts as $part) {
        $subparts = preg_split("/\s+/", $part);

        if (count($subparts) < 0 || count($subparts) > 2) {
           // Too many or too few parts.
           return false;
        }

        if (!in_array($subparts[0], $columns)) {
           // Column name is invalid.
           return false;
        }

        if (count($subparts) == 2 
            && !in_array(strtoupper($subparts[1]), array('ASC', 'DESC')) {
          // ASC or DESC is invalid
          return false;
        }
    }

    return true;
}

Even if the ORDER BY clause is complex, it's still made only out of values you supply (assuming you're not letting users edit it by hand). You can still validate using a white list.

I should also add that I normally don't like to expose my database structure in URLs or other places in the UI and will often alias the stuff in the parameters in the URLs and map it to the real values using a hash.

Rafe
  • 3,056
  • 1
  • 22
  • 25
  • Rafe, I think it would be very valuable to show/explain the whitelist example here. It allows the OP to incorporate his method more defensively, and (looks like) a great technique in general. – Nic Jul 11 '11 at 18:29
  • White-listing isn't trivial to implement when the variable is not a single column name, but an actual chunk of SQL (see updated Q). – mpen Jul 11 '11 at 18:32
  • You don't have to make the $_GET parameter correspond to column names, you could use the parameter in a `case` statement, and based on the parameter value, conditionally add any column(s) or expression(s) you want. – Bill Karwin Jul 11 '11 at 18:56
  • @Rafe: Since when is the square-bracket array syntax supported? I just finished writing a similar function, but it's a little more lenient than yours. Rather than returning true/false it returns the sanitized query, and only discards the parts that aren't in the whitelist. My columns are already aliased so they won't actually see the real column names. Not that it matters too much, there's only a handful of users that can even access the system. – mpen Jul 11 '11 at 19:11
  • @Mark - I always forget that array literals don't work like that in PHP. Been writing too much Ruby lately. – Rafe Jul 11 '11 at 19:14
17

Don't count on the fact that a SQL injection at that point won't currently cause a problem; don't allow ANY SQL injection. If nothing else, a malicious attacker could define a very complex order that could cause serious slowdown of your DB.

Paul Sonier
  • 38,903
  • 3
  • 77
  • 117
6

I prefer to do whitelisting and treat the http parameter string as separate from the string that gets interpolated into the SQL query.

In the following PHP example, the array keys would be the values passed in as http parameters, some kind of symbolic label for different ordering schemes, according to your web interface. The array values would be what we want to interpolate into SQL for these corresponding ordering schemes, e.g. column names or expressions.

<?php

$orderby_whitelist = array(
  "name"    => "last_name, first_name",
  "date"    => "date_created",
  "daterev" => "date_created DESC",
  "DEFAULT" => "id"
);

$order = isset($_GET["order"]) 
  ? $_GET["order"] 
  : "DEFAULT";
$order_expr = array_key_exists($order, $orderby_whitelist) 
  ? $orderby_whitelist[$order] 
  : $orderby_whitelist["DEFAULT"];

mysql_query("SELECT ... FROM ... ORDER BY $order_expr")

This has advantages:

  • You can defend against SQL injection even for cases where you can't use query parameters. If the client passes an unrecognized value, your code ignores it and uses a default order.

  • You don't have to sanitize anything, because the keys and values in the array are both written by you, the programmer. Client input can only pick one of the choices you allow.

  • Your web interface does not reveal your database structure.

  • You can make custom orders that correspond to SQL expressions or alternative ASC/DESC, as I showed above.

  • You can change database structure without changing your web interface, or vice versa.

I cover this solution in my presentation, SQL Injection Myths & Fallacies, and also in my book, SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
2

The logic built around the variable $_GET['order'] should be just as valid when used on a variable $order_sanitized. Why not just sanitize the input first and foremost, and then perform your logic to make it fit into the SELECT statement?

Justin ᚅᚔᚈᚄᚒᚔ
  • 15,081
  • 7
  • 52
  • 64
  • My concern was that in my attempt to sanitize it, I'd essentially have to parse SQL because there's a lot of different valid things they could put in there. If the only problem was that it could throw a SQL error, then I could catch that, or let it display no results and not worry about sanitization. – mpen Jul 11 '11 at 20:33
-1

It still is a point of potential SQL injection. You are depending on the internal implementation of mysql_query. What if in a later version they change it to use multiple queries?

You can use mysql_real_escape_string.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
  • 1
    To what extent, is what I believe the OP is asking. This is a very vague answer, bordering on a comment. – AJ. Jul 11 '11 at 18:04