7

I got bitten my first sql escaping error (it was long overdue) when I tried to execute the PostgreSQL query below with a value containing an apostrophe eg. O'Brien, using FreePascal and Lazarus

SQL.Add(format('select * from zones where upper(zn_name) >=  %s and upper(zn_name) < %s order by zn_name',[sQuote(zoneMin), sQuote(zoneMax)]));

In the query above SQuote is a function that wraps a string in single quotes. Is there some standard library for sanitizing SQL query parameters for Lazarus/FreePascal or Delphi for that matter?

vfclists
  • 19,193
  • 21
  • 73
  • 92

1 Answers1

18

Your application is vulnerable to a serious class of security problems called SQL injection. See http://bobby-tables.com/.

Sure, O'Brian causes an error, but what about ');DROP SCHEMA public;-- ? Or ');DELETE FROM users;-- ? The 1st shouldn't work because your app should never run as a superuser or the user that owns the tables, but few application designers make the effort to actually do that and often run privileged users in production. The 2nd will work in most applications; see the end of the post for details.

The easiest and best preventative measure is to use parameterized statements* in your client library. See this example for Delpi:

To use a prepared statement, do something like this:

query.SQL.Text := 'update people set name=:Name where id=:ID';
query.Prepare;
query.ParamByName( 'Name' ).AsString := name;
query.ParamByName( 'ID' ).AsInteger := id;
query.ExecSQL;

(I've never used Delphi and last wrote Pascal code in 1995; I'm just quoting the example given).

What you are doing currently is string interpolation of parameters. It is very dangerous. It can be done safely only if you have a robust function for quoting SQL literals, one that doesn't just bang quotes on each end, but also handles other escapes, quote doubling, etc. It is the approach of last resort; it's strongly preferable to use a parameterized statement.


Here's an expansion of the example I gave above. Say you're doing a perfectly ordinary insert of a user by username, where 'Fred' is an example username input by the client:

INSERT INTO users ( user_name ) VALUES ('Fred');

Now some unpleasant person sends the username ');DELETE FROM users;--. Suddenly your application is running:

INSERT INTO users ( user_name ) VALUES ('');DELETE FROM users;--');

which when expanded is:

INSERT INTO users ( user_name ) VALUES ('');
DELETE FROM users;
--');

or in other words an insert that inserts an empty string (though they could just as easily put a perfectly valid username in), followed by a DELETE FROM users; statement - deleting all rows in users - then a comment that does nothing. Splat. There goes your data.


* Parameterized statemments are sometimes incorrectly referred to as prepared statements. That's incorrect because a prepared statement isn't necessarily parameterized, and a parameterized statement isn't necessarily prepared. The confusion has arisen because the database interfaces of many languages don't provide a way to use parameterized statements without also using prepared statements.

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • 2
    More so, similar design of libraries exists for PHP (on of 1st steps to mastering it) and all other web scripting languages. It also solves tose problems of string escaping, different DATE representation and such. "Sanitizing" parameters is ad hoc "poor man" replacement for clear parameters separation. If you see someone's code with "sanitizing" instead then most probably he either did not get modern library or just did not get security. – Arioch 'The Nov 01 '12 at 05:50
  • I asked this question because I know this is a minefield even for those who think they have the problem linked. I know about parameterized queries, but want to know if they are guaranteed to be safe, of some more knowledgeable people can trick them as well. – vfclists Nov 01 '12 at 09:31
  • 2
    @vfclists Proper use of parameterized queries will absolutely prevent SQL injection attacks *unless dynamic SQL is executed in the database*, say by a PL/pgSQL `EXECUTE` statement. If you're using dynamic SQL in the DB you need to protect against SQL injection there too, with careful use of the `format` function's `%I` format-specifier and with `EXECUTE ... USING`. See this prior answer for that issue: http://stackoverflow.com/a/12995424/398670 . – Craig Ringer Nov 01 '12 at 09:49
  • 1
    @vfclists You can't use parameters for things like table names, so these must still be interpolated into the query string if they're dynamic - *very* carefully. AFAIK doubling quotes is sufficient for identifier quoting, eg `weird"table name"` becomes `"weird""table name"""`. I have not researched that issue in detail, though. I'd probably read the source code for the PostgreSQL `quote_ident` function if I had to use dynamic identifier names and didn't have a suitable identifier-quoting function provided by my language, to see if there was anything I'd missed. – Craig Ringer Nov 01 '12 at 09:53
  • @vfclists Of course, it's still possible to have one statement safely a value that's later read from the database and used by another statement or app that isn't so careful. For that reason I'd also tend to disallow potentially suspicious sequences where practical; for example, a user name doesn't need to include `');` or `--`. Your app might be careful; that app the stoner workmate wrote on Monday morning might well `SELECT username FROM users` then execute `execsql('UPDATE sometable SET x = y WHERE username = '+username)` thinking that "the value is from the DB so it's safe" though. – Craig Ringer Nov 01 '12 at 09:59
  • @vfclists ... but anyway, that's a whole different question to what you asked: *Is the careful use of parameterized statements sufficient to completely prevent SQL injection*. – Craig Ringer Nov 01 '12 at 10:00
  • Short version: Using a parameterized statement will protect that particular SQL query execution from SQL injection, but not any other SQL that is later constructed using the values from the 1st query, be it by a PL/PgSQL function or by another application consuming the values your statement added to the DB. You can never be lazy and assume the input is safe. It's a lot like cross-site scripting attacks; you have to be paranoid *everywhere*. – Craig Ringer Nov 01 '12 at 10:07
  • Many of the queries will be changing as users will be searching variable a number of fields some of which may have multiple search values with OR so I have to rewrite the query generation to support different numbers of parameters. That will also make the parameters ore interesting – vfclists Nov 01 '12 at 10:36
  • @vfclists Yup, I'd say *painful* personally. That's why things like JPA's Criteria API were created. When JPA Criteria is less awful than the alternative, you know the alternative is really, really bad. – Craig Ringer Nov 01 '12 at 10:46